├── .busted ├── .editorconfig ├── .github └── workflows │ ├── busted.yml │ ├── deploy.yml │ └── luacheck.yml ├── .gitignore ├── .luacheckrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── luassert-scm-1.rockspec ├── rockspecs ├── luassert-1.8.0-0.rockspec └── luassert-1.9.0-1.rockspec ├── spec ├── assertions_spec.lua ├── formatters_spec.lua ├── helper.lua ├── matchers_spec.lua ├── mocks_spec.lua ├── output_spec.lua ├── spies_spec.lua ├── state_spec.lua └── stub_spec.lua └── src ├── array.lua ├── assert.lua ├── assertions.lua ├── compatibility.lua ├── formatters ├── binarystring.lua └── init.lua ├── init.lua ├── languages ├── ar.lua ├── de.lua ├── en.lua ├── fr.lua ├── id.lua ├── is.lua ├── ja.lua ├── ko.lua ├── nl.lua ├── ru.lua ├── ua.lua └── zh.lua ├── match.lua ├── matchers ├── composite.lua ├── core.lua └── init.lua ├── mock.lua ├── modifiers.lua ├── namespaces.lua ├── spy.lua ├── state.lua ├── stub.lua └── util.lua /.busted: -------------------------------------------------------------------------------- 1 | return { 2 | default = { 3 | helper = "./spec/helper.lua", 4 | verbose = true, 5 | output = "gtest", 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | 9 | [*.lua] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [Makefile] 14 | indent_style = tab 15 | 16 | [*.bat] 17 | end_of_line = crlf 18 | -------------------------------------------------------------------------------- /.github/workflows/busted.yml: -------------------------------------------------------------------------------- 1 | name: Busted 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | 7 | busted: 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | luaVersion: [ "5.4", "5.3", "5.2", "5.1", "luajit" ] # , "luajit-openresty" ] 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup ‘lua’ 18 | uses: leafo/gh-actions-lua@v9 19 | with: 20 | luaVersion: ${{ matrix.luaVersion }} 21 | 22 | - name: Setup ‘luarocks’ 23 | uses: leafo/gh-actions-luarocks@v4 24 | 25 | - name: Setup dependencies 26 | run: | 27 | luarocks install busted 28 | luarocks install luacov-coveralls 29 | 30 | - name: Replace system luassert with self 31 | run: | 32 | luarocks remove luassert --force 33 | luarocks make 34 | 35 | - name: Run regression tests 36 | # disable project-local path prefixes to force use of system installation 37 | run: busted --coverage --lpath="" --cpath="" -Xoutput --color 38 | 39 | - name: Report test coverage 40 | if: ${{ success() && github.repository == 'lunarmodules/luassert' }} 41 | continue-on-error: true 42 | run: luacov-coveralls -i src -e .luarocks 43 | env: 44 | COVERALLS_REPO_TOKEN: ${{ github.token }} 45 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: [ push, workflow_dispatch ] 4 | 5 | jobs: 6 | 7 | affected: 8 | uses: lunarmodules/.github/.github/workflows/list_affected_rockspecs.yml@main 9 | 10 | build: 11 | needs: affected 12 | if: ${{ needs.affected.outputs.rockspecs }} 13 | uses: lunarmodules/.github/.github/workflows/test_build_rock.yml@main 14 | with: 15 | rockspecs: ${{ needs.affected.outputs.rockspecs }} 16 | 17 | upload: 18 | needs: [ affected, build ] 19 | # Only run upload if: 20 | # 1. We are on the canonical repository (no uploads from forks) 21 | # 2. The current commit is either tagged or on the default branch (the workflow will upload dev/scm rockspecs any 22 | # time they are touched, tagged ones whenever the edited rockspec and tag match) 23 | # 3. Some rockspecs were changed — this implies the commit changing the rockspec is the same one that gets tagged 24 | if: >- 25 | ${{ 26 | github.repository == 'lunarmodules/luassert' && 27 | ( github.ref_name == 'master' || startsWith(github.ref, 'refs/tags/') ) && 28 | needs.affected.outputs.rockspecs 29 | }} 30 | uses: lunarmodules/.github/.github/workflows/upload_to_luarocks.yml@main 31 | with: 32 | rockspecs: ${{ needs.affected.outputs.rockspecs }} 33 | secrets: 34 | apikey: ${{ secrets.LUAROCKS_APIKEY }} 35 | -------------------------------------------------------------------------------- /.github/workflows/luacheck.yml: -------------------------------------------------------------------------------- 1 | name: Luacheck 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | luacheck: 8 | runs-on: ubuntu-22.04 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | - name: Luacheck 13 | uses: lunarmodules/luacheck@v0 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = "max+busted" 2 | unused_args = false 3 | redefined = false 4 | max_line_length = false 5 | 6 | 7 | globals = { 8 | "randomize", 9 | "match", 10 | "async", 11 | "done", 12 | "busted", 13 | --"ngx.IS_CLI", 14 | } 15 | 16 | 17 | not_globals = { 18 | "string.len", 19 | "table.getn", 20 | } 21 | 22 | 23 | ignore = { 24 | --"6.", -- ignore whitespace warnings 25 | } 26 | 27 | 28 | exclude_files = { 29 | ".luarocks/**", 30 | ".install/**", 31 | } 32 | 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to Luassert 2 | ======================== 3 | 4 | So you want to contribute to luassert? Fantastic! Here's a brief overview on 5 | how best to do so. 6 | 7 | ## What to change 8 | 9 | Here's some examples of things you might want to make a pull request for: 10 | 11 | * New language translations 12 | * New features 13 | * Bugfixes 14 | * Inefficient blocks of code 15 | 16 | If you have a more deeply-rooted problem with how the program is built or some 17 | of the stylistic decisions made in the code, it's best to 18 | [create an issue](https://github.com/Olivine-Labs/luassert/issues) before putting 19 | the effort into a pull request. The same goes for new features - it might be 20 | best to check the project's direction, existing pull requests, and currently open 21 | and closed issues first. 22 | 23 | ## Style 24 | 25 | * Two spaces, not tabs 26 | * Variables have_underscores, classes are Uppercase 27 | * Wrap everything in `local`, expose blocks of code using the module pattern 28 | 29 | Look at existing code to get a good feel for the patterns we use. 30 | 31 | ## Using Git appropriately 32 | 33 | 1. [Fork the repository](https://help.github.com/articles/fork-a-repo) to 34 | your Github account. 35 | 2. Create a *topical branch* - a branch whose name is succinct and explains what 36 | you're doing, such as "klingon-translations" 37 | 3. Make your changes, committing at logical breaks. 38 | 4. Push your branch to your personal account 39 | 5. [Create a pull request](https://help.github.com/articles/using-pull-requests) 40 | 6. Watch for comments or acceptance 41 | 42 | Please note - if you want to change multiple things that don't depend on each 43 | other, make sure you check the master branch back out before making more 44 | changes - that way we can take in each change separately. 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Terms 2 | ================= 3 | 4 | Copyright (c) 2012 Olivine Labs, LLC. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is furnished to do 11 | so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Luassert 2 | ======== 3 | 4 | [![Busted](https://img.shields.io/github/actions/workflow/status/lunarmodules/luassert/busted.yml?branch=master&label=Busted&logo=Lua)](https://github.com/lunarmodules/luassert/actions?workflow=Busted) 5 | [![Luacheck](https://img.shields.io/github/actions/workflow/status/lunarmodules/luassert/luacheck.yml?branch=master&label=Luacheck&logo=Lua)](https://github.com/lunarmodules/luassert/actions?workflow=Luacheck) 6 | [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/lunarmodules/luassert?label=Tag&logo=GitHub)](https://github.com/lunarmodules/luassert/releases) 7 | [![Luarocks](https://img.shields.io/luarocks/v/lunarmodules/luassert?label=Luarocks&logo=Lua)](https://luarocks.org/modules/lunarmodules/luassert) 8 | 9 | luassert extends Lua's built-in assertions to provide additional tests and the ability to create your own. You can modify chains of assertions with `not`. 10 | 11 | Check out [busted](https://lunarmodules.github.io/busted/#asserts) for extended examples. 12 | 13 | ```lua 14 | assert = require("luassert") 15 | 16 | assert.True(true) 17 | assert.is.True(true) 18 | assert.is_true(true) 19 | assert.is_not.True(false) 20 | assert.is.Not.True(false) 21 | assert.is_not_true(false) 22 | assert.are.equal(1, 1) 23 | assert.has.errors(function() error("this should fail") end) 24 | ``` 25 | 26 | Extend your own: 27 | 28 | ```lua 29 | local assert = require("luassert") 30 | local say = require("say") --our i18n lib, installed through luarocks, included as a luassert dependency 31 | 32 | local function has_property(state, arguments) 33 | local property = arguments[1] 34 | local table = arguments[2] 35 | for key, value in pairs(table) do 36 | if key == property then 37 | return true 38 | end 39 | end 40 | return false 41 | end 42 | 43 | say:set_namespace("en") 44 | say:set("assertion.has_property.positive", "Expected property %s in:\n%s") 45 | say:set("assertion.has_property.negative", "Expected property %s to not be in:\n%s") 46 | assert:register("assertion", "has_property", has_property, "assertion.has_property.positive", "assertion.has_property.negative") 47 | 48 | assert.has_property("name", { name = "jack" }) 49 | 50 | ``` 51 | 52 | When writing your own assertions you can also use modifiers to set specific objects to work against. An example 53 | is the [`array` modifier](https://github.com/lunarmodules/luassert/blob/master/src/array.lua) with its 54 | accompanying `holes` assertion. 55 | 56 | Which can be used as; 57 | ```lua 58 | local arr = { "one", "two", "three" } 59 | 60 | assert.array(arr).has.no.holes() -- checks the array to not contain holes --> passes 61 | assert.array(arr).has.no.holes(4) -- sets explicit length to 4 --> fails 62 | ``` 63 | 64 | ## Implementation notes: 65 | 66 | * assertion/modifiers that are Lua keywords (`true`, `false`, `nil`, `function`, and `not`) cannot be used using '.' chaining because that results in compilation errors. Instead chain using '_' (underscore) or use one or more capitals in the reserved word (see code examples above), whatever your coding style prefers 67 | * Most assertions will only take 1 or 2 parameters and an optional failure message, except for the `returned_arguments` assertion, which does not take a failure message 68 | * To specify a custom failure message for the `returned_arguments` assertion, use the `message` modifier 69 | ```lua 70 | local f = function() end 71 | assert.message("the function 'f' did not return 2 arguments").returned_arguments(2, f()) 72 | ``` 73 | 74 | ## Matchers 75 | Argument matching can be performed on spies/stubs with the ability to create your own. This provides flexible argument matching for `called_with` and `returned_with` assertions. Like assertions, you can modify chains of matchers with `not`. Furthermore, matchers can be combined using composite matchers. 76 | ```lua 77 | local assert = require 'luassert' 78 | local match = require 'luassert.match' 79 | local spy = require 'luassert.spy' 80 | 81 | local s = spy.new(function() end) 82 | s('foo') 83 | s(1) 84 | s({}, 'foo') 85 | assert.spy(s).was.called_with(match._) -- arg1 is anything 86 | assert.spy(s).was.called_with(match.is_string()) -- arg1 is a string 87 | assert.spy(s).was.called_with(match.is_number()) -- arg1 is a number 88 | assert.spy(s).was.called_with(match.is_not_true()) -- arg1 is not true 89 | assert.spy(s).was.called_with(match.is_table(), match.is_string()) -- arg1 is a table, arg2 is a string 90 | assert.spy(s).was.called_with(match.has_match('.oo')) -- arg1 contains pattern ".oo" 91 | assert.spy(s).was.called_with({}, 'foo') -- you can still match without using matchers 92 | ``` 93 | Extend your own: 94 | ```lua 95 | local function is_even(state, arguments) 96 | return function(value) 97 | return (value % 2) == 0 98 | end 99 | end 100 | 101 | local function is_gt(state, arguments) 102 | local expected = arguments[1] 103 | return function(value) 104 | return value > expected 105 | end 106 | end 107 | 108 | assert:register("matcher", "even", is_even) 109 | assert:register("matcher", "gt", is_gt) 110 | ``` 111 | ```lua 112 | local assert = require 'luassert' 113 | local match = require 'luassert.match' 114 | local spy = require 'luassert.spy' 115 | 116 | local s = spy.new(function() end) 117 | s(7) 118 | assert.spy(s).was.called_with(match.is_number()) -- arg1 was a number 119 | assert.spy(s).was.called_with(match.is_not_even()) -- arg1 was not even 120 | assert.spy(s).was.called_with(match.is_gt(5)) -- arg1 was greater than 5 121 | ``` 122 | Composite matchers have the form: 123 | ```lua 124 | match.all_of(m1, m2, ...) -- argument matches all of the matchers m1 to mn 125 | match.any_of(m1, m2, ...) -- argument matches at least one of the matchers m1 to mn 126 | match.none_of(m1, m2, ...) -- argument does not match any of the matchers m1 to mn 127 | ``` 128 | 129 | If you're creating a spy for methods that mutate any properties on `self` you should should use `match.is_ref(obj)`: 130 | ```lua 131 | local t = { cnt = 0, } 132 | function t:incrby(i) self.cnt = self.cnt + i end 133 | 134 | local s = spy.on(t, "incrby") 135 | t:incrby(2) 136 | 137 | assert.spy(s).was_called_with(match.is_ref(t), 2) 138 | ``` 139 | 140 | ## Snapshots 141 | To be able to revert changes created by tests, inserting spies and stubs for example, luassert supports 'snapshots'. A snapshot includes the following; 142 | 143 | 1. spies and stubs 144 | 1. parameters 145 | 1. formatters 146 | 147 | Example: 148 | ```lua 149 | describe("Showing use of snapshots", function() 150 | local snapshot 151 | 152 | before_each(function() 153 | snapshot = assert:snapshot() 154 | end) 155 | 156 | after_each(function() 157 | snapshot:revert() 158 | end) 159 | 160 | it("does some test", function() 161 | -- spies or stubs registered here, parameters changed, or formatters added 162 | -- will be undone in the after_each() handler. 163 | end) 164 | 165 | end) 166 | ``` 167 | 168 | ## Parameters 169 | To register state information 'parameters' can be used. The parameter is included in a snapshot and can hence be restored in between tests. For an example see `Configuring table depth display` below. 170 | 171 | Example: 172 | ```lua 173 | assert:set_parameter("my_param_name", 1) 174 | local s = assert:snapshot() 175 | assert:set_parameter("my_param_name", 2) 176 | s:revert() 177 | assert.are.equal(1, assert:get_parameter("my_param_name")) 178 | ``` 179 | 180 | ## Customizing argument formatting 181 | luassert comes preloaded with argument formatters for common Lua types, but it is easy to roll your own. Customizing them is especially useful for limiting table depth and for userdata types. 182 | 183 | ### Configuring table depth display 184 | The default table formatter allows you to customize the levels displayed by setting the `TableFormatLevel` parameter (setting it to -1 displays all levels). 185 | 186 | Example: 187 | ```lua 188 | describe("Tests different levels of table display", function() 189 | 190 | local testtable = { 191 | hello = "hola", 192 | world = "mundo", 193 | liqour = { 194 | "beer", "wine", "water" 195 | }, 196 | fruit = { 197 | native = { "apple", "strawberry", "grape" }, 198 | tropical = { "banana", "orange", "mango" }, 199 | }, 200 | } 201 | 202 | it("tests display of 0 levels", function() 203 | assert:set_parameter("TableFormatLevel", 0) 204 | assert.are.same(testtable, {}) 205 | end) 206 | 207 | it("tests display of 2 levels", function() 208 | assert:set_parameter("TableFormatLevel", 2) 209 | assert.are.same(testtable, {}) 210 | end) 211 | 212 | end) 213 | ``` 214 | 215 | Will display the following output with the table pretty-printed to the requested depth: 216 | ``` 217 | Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 45 218 | tests display of 0 levels 219 | ...ua projects\busted\formatter\spec\formatter_spec.lua:47: Expected objects to be the same. Passed in: 220 | (table): { } 221 | Expected: 222 | (table): { ... more } 223 | 224 | Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 50 225 | tests display of 2 levels 226 | ...ua projects\busted\formatter\spec\formatter_spec.lua:52: Expected objects to be the same. Passed in: 227 | (table): { } 228 | Expected: 229 | (table): { 230 | [hello] = 'hola' 231 | [fruit] = { 232 | [tropical] = { ... more } 233 | [native] = { ... more } } 234 | [liqour] = { 235 | [1] = 'beer' 236 | [2] = 'wine' 237 | [3] = 'water' } 238 | [world] = 'mundo' } 239 | ``` 240 | ### Customized formatters 241 | The formatters are functions taking a single argument that needs to be converted to a string representation. The formatter should examine the value provided, if it can format the value, it should return the formatted string, otherwise it should return `nil`. 242 | Formatters can be added through `assert:add_formatter(formatter_func)`, and removed by calling `assert:remove_formatter(formatter_func)`. 243 | 244 | Example using the included binary string formatter: 245 | ```lua 246 | local binstring = require("luassert.formatters.binarystring") 247 | 248 | describe("Tests using a binary string formatter", function() 249 | 250 | setup(function() 251 | assert:add_formatter(binstring) 252 | end) 253 | 254 | teardown(function() 255 | assert:remove_formatter(binstring) 256 | end) 257 | 258 | it("tests a string comparison with binary formatting", function() 259 | local s1, s2 = "", "" 260 | for n = 65,88 do 261 | s1 = s1 .. string.char(n) 262 | s2 = string.char(n) .. s2 263 | end 264 | assert.are.same(s1, s2) 265 | 266 | end) 267 | 268 | end) 269 | ``` 270 | 271 | Because this formatter formats string values, and is added last, it will take precedence over the regular string formatter. The results will be: 272 | ``` 273 | Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 13 274 | tests a string comparison with binary formatting 275 | ...ua projects\busted\formatter\spec\formatter_spec.lua:19: Expected objects to be the same. Passed in: 276 | Binary string length; 24 bytes 277 | 58 57 56 55 54 53 52 51 50 4f 4e 4d 4c 4b 4a 49 XWVUTSRQ PONMLKJI 278 | 48 47 46 45 44 43 42 41 HGFEDCBA 279 | 280 | Expected: 281 | Binary string length; 24 bytes 282 | 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 ABCDEFGH IJKLMNOP 283 | 51 52 53 54 55 56 57 58 QRSTUVWX 284 | ``` 285 | 286 | -------------------------------------------------------------------------------- /luassert-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "luassert" 2 | local package_version = "scm" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | rockspec_format = "3.0" 8 | package = package_name 9 | version = package_version .. "-" .. rockspec_revision 10 | 11 | source = { 12 | url = "git+https://github.com/" .. github_account_name .. "/" .. github_repo_name .. ".git" 13 | } 14 | 15 | if package_version == "scm" then source.branch = "master" else source.tag = "v" .. package_version end 16 | 17 | description = { 18 | summary = "Lua assertions extension", 19 | detailed = [[ 20 | Adds a framework that allows registering new assertions 21 | without compromising builtin assertion functionality. 22 | ]], 23 | homepage = "https://lunarmodules.github.io/busted/", 24 | license = "MIT " 25 | } 26 | 27 | dependencies = { 28 | "lua >= 5.1", 29 | "say >= 1.4.0-1" 30 | } 31 | 32 | test_dependencies = { 33 | "busted", 34 | } 35 | 36 | test = { 37 | type = "busted", 38 | } 39 | 40 | build = { 41 | type = "builtin", 42 | modules = { 43 | ["luassert.compatibility"] = "src/compatibility.lua", 44 | ["luassert.state"] = "src/state.lua", 45 | ["luassert.util"] = "src/util.lua", 46 | ["luassert.spy"] = "src/spy.lua", 47 | ["luassert.stub"] = "src/stub.lua", 48 | ["luassert.assert"] = "src/assert.lua", 49 | ["luassert.modifiers"] = "src/modifiers.lua", 50 | ["luassert.assertions"] = "src/assertions.lua", 51 | ["luassert.array"] = "src/array.lua", 52 | ["luassert.namespaces"] = "src/namespaces.lua", 53 | ["luassert.match"] = "src/match.lua", 54 | ["luassert.mock"] = "src/mock.lua", 55 | ["luassert.init"] = "src/init.lua", 56 | ["luassert.matchers.init"] = "src/matchers/init.lua", 57 | ["luassert.matchers.core"] = "src/matchers/core.lua", 58 | ["luassert.matchers.composite"] = "src/matchers/composite.lua", 59 | ["luassert.formatters.init"] = "src/formatters/init.lua", 60 | ["luassert.formatters.binarystring"] = "src/formatters/binarystring.lua", 61 | ["luassert.languages.ar"] = "src/languages/ar.lua", 62 | ["luassert.languages.de"] = "src/languages/de.lua", 63 | ["luassert.languages.en"] = "src/languages/en.lua", 64 | ["luassert.languages.fr"] = "src/languages/fr.lua", 65 | ["luassert.languages.is"] = "src/languages/is.lua", 66 | ["luassert.languages.ja"] = "src/languages/ja.lua", 67 | ["luassert.languages.ko"] = "src/languages/ko.lua", 68 | ["luassert.languages.nl"] = "src/languages/nl.lua", 69 | ["luassert.languages.ru"] = "src/languages/ru.lua", 70 | ["luassert.languages.ua"] = "src/languages/ua.lua", 71 | ["luassert.languages.zh"] = "src/languages/zh.lua", 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rockspecs/luassert-1.8.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "luassert" 2 | version = "1.8.0-0" 3 | source = { 4 | url = "https://github.com/Olivine-Labs/luassert/archive/v1.8.0.tar.gz", 5 | dir = "luassert-1.8.0" 6 | } 7 | description = { 8 | summary = "Lua Assertions Extension", 9 | detailed = [[ 10 | Adds a framework that allows registering new assertions 11 | without compromising builtin assertion functionality. 12 | ]], 13 | homepage = "http://olivinelabs.com/busted/", 14 | license = "MIT " 15 | } 16 | dependencies = { 17 | "lua >= 5.1", 18 | "say >= 1.2-1" 19 | } 20 | build = { 21 | type = "builtin", 22 | modules = { 23 | ["luassert.compatibility"] = "src/compatibility.lua", 24 | ["luassert.state"] = "src/state.lua", 25 | ["luassert.util"] = "src/util.lua", 26 | ["luassert.spy"] = "src/spy.lua", 27 | ["luassert.stub"] = "src/stub.lua", 28 | ["luassert.assert"] = "src/assert.lua", 29 | ["luassert.modifiers"] = "src/modifiers.lua", 30 | ["luassert.assertions"] = "src/assertions.lua", 31 | ["luassert.array"] = "src/array.lua", 32 | ["luassert.namespaces"] = "src/namespaces.lua", 33 | ["luassert.match"] = "src/match.lua", 34 | ["luassert.mock"] = "src/mock.lua", 35 | ["luassert.init"] = "src/init.lua", 36 | ["luassert.matchers.init"] = "src/matchers/init.lua", 37 | ["luassert.matchers.core"] = "src/matchers/core.lua", 38 | ["luassert.matchers.composite"] = "src/matchers/composite.lua", 39 | ["luassert.formatters.init"] = "src/formatters/init.lua", 40 | ["luassert.formatters.binarystring"] = "src/formatters/binarystring.lua", 41 | ["luassert.languages.en"] = "src/languages/en.lua", 42 | ["luassert.languages.ar"] = "src/languages/ar.lua", 43 | ["luassert.languages.fr"] = "src/languages/fr.lua", 44 | ["luassert.languages.nl"] = "src/languages/nl.lua", 45 | ["luassert.languages.ru"] = "src/languages/ru.lua", 46 | ["luassert.languages.ua"] = "src/languages/ua.lua", 47 | ["luassert.languages.zh"] = "src/languages/zh.lua", 48 | ["luassert.languages.ja"] = "src/languages/ja.lua", 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rockspecs/luassert-1.9.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "luassert" 2 | local package_version = "1.9.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | package = package_name 8 | version = package_version .. "-" .. rockspec_revision 9 | 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git" 12 | } 13 | 14 | if package_version == "scm" then source.branch = "master" else source.tag = "v" .. package_version end 15 | 16 | description = { 17 | summary = "Lua assertions extension", 18 | detailed = [[ 19 | Adds a framework that allows registering new assertions 20 | without compromising builtin assertion functionality. 21 | ]], 22 | homepage = "https://lunarmodules.github.io/busted/", 23 | license = "MIT " 24 | } 25 | 26 | dependencies = { 27 | "lua >= 5.1", 28 | "say >= 1.4.0-1" 29 | } 30 | 31 | build = { 32 | type = "builtin", 33 | modules = { 34 | ["luassert.compatibility"] = "src/compatibility.lua", 35 | ["luassert.state"] = "src/state.lua", 36 | ["luassert.util"] = "src/util.lua", 37 | ["luassert.spy"] = "src/spy.lua", 38 | ["luassert.stub"] = "src/stub.lua", 39 | ["luassert.assert"] = "src/assert.lua", 40 | ["luassert.modifiers"] = "src/modifiers.lua", 41 | ["luassert.assertions"] = "src/assertions.lua", 42 | ["luassert.array"] = "src/array.lua", 43 | ["luassert.namespaces"] = "src/namespaces.lua", 44 | ["luassert.match"] = "src/match.lua", 45 | ["luassert.mock"] = "src/mock.lua", 46 | ["luassert.init"] = "src/init.lua", 47 | ["luassert.matchers.init"] = "src/matchers/init.lua", 48 | ["luassert.matchers.core"] = "src/matchers/core.lua", 49 | ["luassert.matchers.composite"] = "src/matchers/composite.lua", 50 | ["luassert.formatters.init"] = "src/formatters/init.lua", 51 | ["luassert.formatters.binarystring"] = "src/formatters/binarystring.lua", 52 | ["luassert.languages.ar"] = "src/languages/ar.lua", 53 | ["luassert.languages.de"] = "src/languages/de.lua", 54 | ["luassert.languages.en"] = "src/languages/en.lua", 55 | ["luassert.languages.fr"] = "src/languages/fr.lua", 56 | ["luassert.languages.is"] = "src/languages/is.lua", 57 | ["luassert.languages.ja"] = "src/languages/ja.lua", 58 | ["luassert.languages.nl"] = "src/languages/nl.lua", 59 | ["luassert.languages.ru"] = "src/languages/ru.lua", 60 | ["luassert.languages.ua"] = "src/languages/ua.lua", 61 | ["luassert.languages.zh"] = "src/languages/zh.lua", 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /spec/assertions_spec.lua: -------------------------------------------------------------------------------- 1 | local tablex = require 'pl.tablex' 2 | 3 | describe("Test Assertions", function() 4 | it("Tests backward compatible assert() functionality", function() 5 | local test = true 6 | local message = "the message" 7 | local third_arg = "three" 8 | local fourth_arg = "four" 9 | local one, two, three, four, five = assert(test, message, third_arg, fourth_arg) 10 | assert(one == test and two == message and three == third_arg and 11 | four == fourth_arg and five == nil, 12 | "Expected input values to be outputted as well when an assertion does not fail") 13 | end) 14 | 15 | it("Checks assert() handles more than two return values", function() 16 | local res, err = pcall(assert, false, "some error", "a string") 17 | assert(not res) 18 | 19 | err = tostring(err) 20 | assert(not err:match("attempt to perform arithmetic on a string value", nil, true)) 21 | assert(err:match("some error", nil, true)) 22 | end) 23 | 24 | it("Checks level and get_level values", function() 25 | assert.equal(3, assert:get_level(assert:level(3))) 26 | assert.is.Nil(assert:get_level({})) 27 | assert.is.Nil(assert:get_level("hello world")) 28 | assert.is.Nil(assert:get_level(nil)) 29 | end) 30 | 31 | it("Checks asserts can be reused", function() 32 | local is_same = assert.is_same 33 | local orig_same = tablex.deepcopy(is_same) 34 | is_same({}, {}) 35 | assert.is_same(orig_same, is_same) 36 | end) 37 | 38 | it("Checks to see if tables 1 and 2 are the same", function() 39 | local table1 = { derp = false} 40 | local table2 = { derp = false} 41 | assert.same(table1, table2) 42 | 43 | if type(jit) == "table" then 44 | loadstring([[ 45 | local assert = require 'luassert' 46 | assert.same(0ULL, 0) 47 | assert.same(0, 0ULL) 48 | assert.same({0ULL}, {0}) 49 | assert.same({0}, {0ULL}) 50 | ]])() 51 | end 52 | end) 53 | 54 | it("Checks to see if tables 1 and 2 are not the same", function() 55 | local table1 = { derp = false} 56 | local table2 = { derp = true} 57 | assert.is_not.same(table1, table2) 58 | end) 59 | 60 | it("Checks the same() assertion for tables with protected metatables", function() 61 | local troubleSomeTable = {} 62 | setmetatable(troubleSomeTable, {__metatable = 0}) 63 | assert.are.same(troubleSomeTable, troubleSomeTable) 64 | end) 65 | 66 | it("Checks same() assertion to handle nils properly", function() 67 | assert.is.error(function() assert.same(nil) end) -- minimum 2 arguments 68 | assert.same(nil, nil) 69 | assert.is_not.same("a string", nil) 70 | assert.is_not.same(nil, "a string") 71 | end) 72 | 73 | it("Checks same() assertion ignores __pairs metamethod", function() 74 | local t1 = setmetatable({1,2,3}, {__pairs = function(t) return nil end}) 75 | local t2 = {1,2,3} 76 | assert.same(t1, t2) 77 | assert.same(t2, t1) 78 | end) 79 | 80 | it("Checks same() assertion to handle recursive tables", function() 81 | local t1 = { k1 = 1, k2 = 2 } 82 | local t2 = { k1 = 1, k2 = 2 } 83 | local t3 = { k1 = 1, k2 = 2, k3 = { k1 = 1, k2 = 2, k3 = t2 } } 84 | t1.k3 = t1 85 | t2.k3 = t2 86 | 87 | assert.same(t1, t2) 88 | assert.same(t1, t3) 89 | assert.same(t1, t3) 90 | end) 91 | 92 | it("Checks same() assertion to handle recursive tables that don't match", function() 93 | local t1 = {} 94 | local t2 = {} 95 | local a = {} 96 | local b = {} 97 | local c = {} 98 | local d = {} 99 | t1.k1 = a 100 | t2.k1 = b 101 | a.k1 = c 102 | b.k1 = d 103 | c.k2 = a 104 | d.k2 = d 105 | assert.is_table(t1.k1.k1.k2.k1) 106 | assert.is_nil(t2.k1.k1.k2.k1) 107 | assert.are_not_same(t1, t2) 108 | end) 109 | 110 | it("Checks same() assertion to handle recursive tables that don't match - deeper recursion", function() 111 | local cycle_root = {} 112 | local cycle_1 = {} 113 | local cycle_2 = {} 114 | cycle_root.k1 = cycle_1 115 | cycle_1.k2 = cycle_2 116 | cycle_2.k2 = cycle_root 117 | 118 | local mimic_root = {} 119 | local mimic_1 = {} 120 | local mimic_2 = {} 121 | local mimic_3 = {} 122 | local self_ref = {} 123 | mimic_root.k1 = mimic_1 124 | mimic_1.k2 = mimic_2 125 | mimic_2.k2 = mimic_3 126 | mimic_3.k1 = self_ref 127 | self_ref.k2 = self_ref 128 | 129 | assert.is_table(cycle_root.k1.k2.k2.k1.k2.k2.k1) 130 | assert.is_nil(mimic_root.k1.k2.k2.k1.k2.k2.k1) 131 | assert.are_not_same(cycle_root, mimic_root) 132 | end) 133 | 134 | it("Checks same() assertion to handle recursive tables that don't match - multiple recursions", function() 135 | local c1, c2, c3, c4 = {}, {}, {}, {} 136 | local m1, m2, m3, m4, m5, m6, m7, m8, m9 = {}, {}, {}, {}, {}, {}, {}, {}, {} 137 | local r1, r2, r3 = {}, {}, {} 138 | 139 | r1[1] = r3 140 | r2[1] = r2 141 | r3[1] = r3 142 | c2[1] = r2 143 | c3[1] = r2 144 | c4[1] = r2 145 | m2[1] = r3 146 | m3[1] = r3 147 | m4[1] = r3 148 | m6[1] = r3 149 | m7[1] = r3 150 | m8[1] = r3 151 | 152 | c1[2] = c2 153 | c2[3] = c3 154 | c3[3] = c4 155 | c4[3] = c1 156 | 157 | m1[2] = m2 158 | m2[3] = m3 159 | m3[3] = m4 160 | m4[3] = m5 161 | m5[2] = m6 162 | m6[3] = m7 163 | m7[3] = m8 164 | m8[3] = m9 165 | m9[2] = r1 166 | r1[3] = r1 167 | 168 | assert.is_table(c1[2][3][3][3][2][3][3][3][2][3][3][3][2]) 169 | assert.is_nil(m1[2][3][3][3][2][3][3][3][2][3][3][3][2]) 170 | assert.are_not_same(c1, m1) 171 | end) 172 | 173 | it("Checks to see if tables 1 and 2 are equal", function() 174 | local table1 = { derp = false} 175 | local table2 = table1 176 | assert.equals(table1, table2) 177 | end) 178 | 179 | it("Checks equals() assertion to handle nils properly", function() 180 | assert.is.error(function() assert.equals(nil) end) -- minimum 2 arguments 181 | assert.equals(nil, nil) 182 | assert.is_not.equals("a string", nil) 183 | assert.is_not.equals(nil, "a string") 184 | end) 185 | 186 | it("Checks to see if table1 only contains unique elements", function() 187 | local table2 = { derp = false} 188 | local table3 = { derp = true } 189 | local table1 = {table2,table3} 190 | local tablenotunique = {table2,table2} 191 | assert.is.unique(table1) 192 | assert.is_not.unique(tablenotunique) 193 | end) 194 | 195 | it("Checks near() assertion handles tolerances", function() 196 | assert.is.error(function() assert.near(0) end) -- minimum 3 arguments 197 | assert.is.error(function() assert.near(0, 0) end) -- minimum 3 arguments 198 | assert.is.error(function() assert.near('a', 0, 0) end) -- arg1 must be convertable to number 199 | assert.is.error(function() assert.near(0, 'a', 0) end) -- arg2 must be convertable to number 200 | assert.is.error(function() assert.near(0, 0, 'a') end) -- arg3 must be convertable to number 201 | assert.is.near(1.5, 2.0, 0.5) 202 | assert.is.near('1.5', '2.0', '0.5') 203 | assert.is_not.near(1.5, 2.0, 0.499) 204 | assert.is_not.near('1.5', '2.0', '0.499') 205 | end) 206 | 207 | it("Checks matches() assertion does string matching", function() 208 | assert.is.error(function() assert.matches('.*') end) -- minimum 2 arguments 209 | assert.is.error(function() assert.matches(nil, 's') end) -- arg1 must be a string 210 | assert.is.error(function() assert.matches('s', {}) end) -- arg2 must be convertable to string 211 | assert.is.error(function() assert.matches('s', 's', 's', 's') end) -- arg3 or arg4 must be a number or nil 212 | assert.matches("%w+", "test") 213 | assert.has.match("%w+", "test") 214 | assert.has_no.match("%d+", "derp") 215 | assert.has.match("test", "test", nil, true) 216 | assert.has_no.match("%w+", "test", nil, true) 217 | assert.has.match("^test", "123 test", 5) 218 | assert.has_no.match("%d+", "123 test", '4') 219 | end) 220 | 221 | it("Ensures the is operator doesn't change the behavior of equals", function() 222 | assert.is.equals(true, true) 223 | end) 224 | 225 | it("Ensures the is_not operator does change the behavior of equals", function() 226 | assert.is_not.equals(true, false) 227 | end) 228 | 229 | it("Ensures that error only throws an error when the first argument function does not throw an error", function() 230 | local test_function = function() error("test") end 231 | assert.has.error(test_function) 232 | assert.has.error(test_function, "test") 233 | assert.has_no.errors(test_function, "derp") 234 | end) 235 | 236 | it("Checks to see if var is truthy", function() 237 | assert.is_not.truthy(nil) 238 | assert.is.truthy(true) 239 | assert.is.truthy({}) 240 | assert.is.truthy(function() end) 241 | assert.is.truthy("") 242 | assert.is_not.truthy(false) 243 | assert.error(function() assert.truthy(false) end) 244 | end) 245 | 246 | it("Checks to see if var is falsy", function() 247 | assert.is.falsy(nil) 248 | assert.is_not.falsy(true) 249 | assert.is_not.falsy({}) 250 | assert.is_not.falsy(function() end) 251 | assert.is_not.falsy("") 252 | assert.is.falsy(false) 253 | end) 254 | 255 | it("Ensures the Not operator does change the behavior of equals", function() 256 | assert.is.Not.equal(true, false) 257 | end) 258 | 259 | it("Checks true() assertion", function() 260 | assert.is.True(true) 261 | assert.is.Not.True(123) 262 | assert.is.Not.True(nil) 263 | assert.is.Not.True("abc") 264 | assert.is.Not.True(false) 265 | assert.is.Not.True(function() end) 266 | end) 267 | 268 | it("Checks false() assertion", function() 269 | assert.is.False(false) 270 | assert.is.Not.False(123) 271 | assert.is.Not.False(nil) 272 | assert.is.Not.False("abc") 273 | assert.is.Not.False(true) 274 | assert.is.Not.False(function() end) 275 | end) 276 | 277 | it("Checks boolean() assertion", function() 278 | assert.is.boolean(false) 279 | assert.is.boolean(true) 280 | assert.is.Not.boolean(123) 281 | assert.is.Not.boolean(nil) 282 | assert.is.Not.boolean("abc") 283 | assert.is.Not.boolean(function() end) 284 | end) 285 | 286 | it("Checks number() assertion", function() 287 | assert.is.number(123) 288 | assert.is.number(-0.345) 289 | assert.is.Not.number(nil) 290 | assert.is.Not.number("abc") 291 | assert.is.Not.number(true) 292 | assert.is.Not.number(function() end) 293 | end) 294 | 295 | it("Checks string() assertion", function() 296 | assert.is.string("abc") 297 | assert.is.Not.string(123) 298 | assert.is.Not.string(nil) 299 | assert.is.Not.string(true) 300 | assert.is.Not.string(function() end) 301 | end) 302 | 303 | it("Checks table() assertion", function() 304 | assert.is.table({}) 305 | assert.is.Not.table("abc") 306 | assert.is.Not.table(123) 307 | assert.is.Not.table(nil) 308 | assert.is.Not.table(true) 309 | assert.is.Not.table(function() end) 310 | end) 311 | 312 | it("Checks nil() assertion", function() 313 | assert.is.Nil(nil) 314 | assert.is.Not.Nil(123) 315 | assert.is.Not.Nil("abc") 316 | assert.is.Not.Nil(true) 317 | assert.is.Not.Nil(function() end) 318 | end) 319 | 320 | it("Checks function() assertion", function() 321 | assert.is.Function(function() end) 322 | assert.is.Not.Function(nil) 323 | assert.is.Not.Function(123) 324 | assert.is.Not.Function("abc") 325 | assert.is.Not.Function(true) 326 | end) 327 | 328 | it("Checks userdata() assertion", function() 329 | local myfile = io.tmpfile() 330 | assert.is.userdata(myfile) 331 | myfile:close() 332 | assert.is.Not.userdata(nil) 333 | assert.is.Not.userdata(123) 334 | assert.is.Not.userdata("abc") 335 | assert.is.Not.userdata(true) 336 | assert.is.Not.userdata(function() end) 337 | end) 338 | 339 | it("Checks thread() assertion", function() 340 | local mythread = coroutine.create(function() end) 341 | assert.is.thread(mythread) 342 | assert.is.Not.thread(nil) 343 | assert.is.Not.thread(123) 344 | assert.is.Not.thread("abc") 345 | assert.is.Not.thread(true) 346 | assert.is.Not.thread(function() end) 347 | end) 348 | 349 | it("Checks '_' chaining of modifiers and assertions", function() 350 | assert.is_string("abc") 351 | assert.is_a_string("abc") 352 | assert.is_true(true) 353 | assert.is_not_string(123) 354 | assert.is_not_a_string(123) 355 | assert.is_nil(nil) 356 | assert.is_not_nil({}) 357 | assert.is_not_true(false) 358 | assert.is_not_false(true) 359 | assert.are_the_same({a=1}, {a=1}) 360 | assert.are_not_the_same({a=1}, {b=1}) 361 | 362 | -- verify that failing assertions actually fail 363 | assert.has_error(function() assert.is_string(1) end) 364 | assert.has_error(function() assert.is_true(false) end) 365 | assert.has_error(function() assert.is_not_string('string!') end) 366 | assert.has_error(function() assert.is_not_a_string('string!') end) 367 | assert.has_error(function() assert.is_nil({}) end) 368 | assert.has_error(function() assert.is_not_nil(nil) end) 369 | assert.has_error(function() assert.is_not_true(true) end) 370 | assert.has_error(function() assert.is_not_false(false) end) 371 | assert.has_error(function() assert.are_the_same({a=1}, {b=1}) end) 372 | assert.has_a_error(function() assert.are_not_the_same({a=1}, {a=1}) end) 373 | assert.has_an_error(function() assert.are_not_the_same({a=1}, {a=1}) end) 374 | end) 375 | 376 | it("Checks '.' chaining of modifiers and assertions", function() 377 | assert.is.string("abc") 378 | assert.is.a.string("abc") 379 | assert.is.True(true) 380 | assert.is.Not.string(123) 381 | assert.is.Not.a.string(123) 382 | assert.is.Nil(nil) 383 | assert.is.Not.Nil({}) 384 | assert.is.Not.True(false) 385 | assert.is.Not.False(true) 386 | assert.equals.Not(true, false) 387 | assert.equals.Not.Not(true, true) 388 | assert.Not.equals.Not(true, true) 389 | assert.are.the.same({a=1}, {a=1}) 390 | assert.are.Not.the.same({a=1}, {b=1}) 391 | 392 | -- verify that failing assertions actually fail 393 | assert.has.error(function() assert.is.string(1) end) 394 | assert.has.error(function() assert.is.a.string(1) end) 395 | assert.has.error(function() assert.is.True(false) end) 396 | assert.has.error(function() assert.is.Not.string('string!') end) 397 | assert.has.error(function() assert.is.Not.a.string('string!') end) 398 | assert.has.error(function() assert.is.Nil({}) end) 399 | assert.has.error(function() assert.is.Not.Nil(nil) end) 400 | assert.has.error(function() assert.is.Not.True(true) end) 401 | assert.has.error(function() assert.is.Not.False(false) end) 402 | assert.has.error(function() assert.equals.Not(true, true) end) 403 | assert.has.error(function() assert.equals.Not.Not(true, false) end) 404 | assert.has.error(function() assert.Not.equals.Not(true, false) end) 405 | assert.has.error(function() assert.are.the.same({a=1}, {b=1}) end) 406 | assert.has.a.error(function() assert.are.Not.the.same({a=1}, {a=1}) end) 407 | assert.has.an.error(function() assert.are.Not.the.same({a=1}, {a=1}) end) 408 | end) 409 | 410 | it("Checks number of returned arguments", function() 411 | local fn = function() 412 | end 413 | 414 | local fn1 = function() 415 | return "something",2,3 416 | end 417 | 418 | local fn2 = function() 419 | return nil 420 | end 421 | 422 | local fn3 = function() 423 | return nil, nil 424 | end 425 | 426 | local fn4 = function() 427 | return nil, 1, nil 428 | end 429 | 430 | assert.returned_arguments(0, fn()) 431 | assert.not_returned_arguments(2, fn1()) 432 | assert.returned_arguments(3, fn1()) 433 | 434 | assert.returned_arguments(1, fn2()) 435 | assert.returned_arguments(2, fn3()) 436 | assert.returned_arguments(3, fn4()) 437 | end) 438 | 439 | it("Checks has_error to accept only callable arguments", function() 440 | local t_ok = setmetatable( {}, { __call = function() end } ) 441 | local t_nok = setmetatable( {}, { __call = function() error("some error") end } ) 442 | local f_ok = function() end 443 | local f_nok = function() error("some error") end 444 | 445 | assert.has_error(f_nok) 446 | assert.has_no_error(f_ok) 447 | assert.has_error(t_nok) 448 | assert.has_no_error(t_ok) 449 | end) 450 | 451 | it("Checks has_error compares error strings", function() 452 | assert.has_error(function() error() end) 453 | assert.has_error(function() error("string") end, "string") 454 | end) 455 | 456 | it("Checks has_error compares error objects", function() 457 | local func = function() end 458 | assert.has_error(function() error({ "table" }) end, { "table" }) 459 | assert.has_error(function() error(func) end, func) 460 | assert.has_error(function() error(false) end, false) 461 | assert.has_error(function() error(true) end, true) 462 | assert.has_error(function() error(0) end, 0) 463 | assert.has_error(function() error(1.5) end, 1.5) 464 | assert.has_error(function() error(10.0^50) end, 10.0^50) 465 | assert.has_error(function() error(10.0^-50) end, 10.0^-50) 466 | assert.has_no_error(function() error(true) end, 0) 467 | assert.has_no_error(function() error(125) end, 1.5) 468 | end) 469 | 470 | it("Checks has_error compares error objects with strings", function() 471 | local mt = { __tostring = function(t) return t[1] end } 472 | assert.has_error(function() error(setmetatable({ "table" }, mt)) end, "table") 473 | end) 474 | 475 | it("Checks error_matches to accepts at least 2 arguments", function() 476 | assert.has_error(function() assert.error_matches(error) end) 477 | assert.has_no_error(function() assert.error_matches(function() error("foo") end, ".*") end) 478 | end) 479 | 480 | it("Checks error_matches to accept only callable arguments", function() 481 | local t_ok = setmetatable( {}, { __call = function() end } ) 482 | local t_nok = setmetatable( {}, { __call = function() error("some error") end } ) 483 | local f_ok = function() end 484 | local f_nok = function() error("some error") end 485 | 486 | assert.error_matches(f_nok, ".*") 487 | assert.no_error_matches(f_ok, ".*") 488 | assert.error_matches(t_nok, ".*") 489 | assert.no_error_matches(t_ok, ".*") 490 | end) 491 | 492 | it("Checks error_matches compares error strings with pattern", function() 493 | assert.error_matches(function() error() end, nil) 494 | assert.no_error_matches(function() end, nil) 495 | assert.does_error_match(function() error(123) end, "^%d+$") 496 | assert.error.matches(function() error("string") end, "^%w+$") 497 | assert.matches.error(function() error("string") end, "str", nil, true) 498 | assert.matches_error(function() error("123string") end, "^[^0-9]+", 4) 499 | assert.has_no_error.match(function() error("123string") end, "123", 4, true) 500 | assert.does_not.match_error(function() error("string") end, "^%w+$", nil, true) 501 | end) 502 | 503 | it("Checks error_matches does not compare error objects", function() 504 | assert.no_error_matches(function() error({ "table" }) end, "table") 505 | end) 506 | 507 | it("Checks error_matches compares error objects that are convertible to strings", function() 508 | local mt = { __tostring = function(t) return t[1] end } 509 | assert.error_matches(function() error(setmetatable({ "table" }, mt)) end, "^table$") 510 | end) 511 | 512 | it("Checks register creates custom assertions", function() 513 | local say = require("say") 514 | 515 | local function has_property(state, arguments) 516 | local property = arguments[1] 517 | local table = arguments[2] 518 | for key, value in pairs(table) do 519 | if key == property then 520 | return true 521 | end 522 | end 523 | return false 524 | end 525 | 526 | say:set_namespace("en") 527 | say:set("assertion.has_property.positive", "Expected property %s in:\n%s") 528 | say:set("assertion.has_property.negative", "Expected property %s to not be in:\n%s") 529 | assert:register("assertion", "has_property", has_property, "assertion.has_property.positive", "assertion.has_property.negative") 530 | 531 | assert.has_property("name", { name = "jack" }) 532 | assert.has.property("name", { name = "jack" }) 533 | assert.not_has_property("surname", { name = "jack" }) 534 | assert.Not.has.property("surname", { name = "jack" }) 535 | assert.has_error(function() assert.has_property("surname", { name = "jack" }) end) 536 | assert.has_error(function() assert.has.property("surname", { name = "jack" }) end) 537 | assert.has_error(function() assert.no_has_property("name", { name = "jack" }) end) 538 | assert.has_error(function() assert.no.has.property("name", { name = "jack" }) end) 539 | end) 540 | 541 | it("Checks unregister removes assertions", function() 542 | assert.has_no_error(function() assert.has_property("name", { name = "jack" }) end) 543 | 544 | assert:unregister("assertion", "has_property") 545 | 546 | assert.has_error(function() assert.has_property("name", { name = "jack" }) end, "luassert: unknown modifier/assertion: 'has_property'") 547 | end) 548 | 549 | it("Checks asserts return all their arguments on success", function() 550 | assert.is_same({true, "string"}, {assert(true, "string")}) 551 | assert.is_same({true, "bar"}, {assert.is_true(true, "bar")}) 552 | assert.is_same({false, "foobar"}, {assert.is_false(false, "foobar")}) 553 | assert.is_same({"", "truthy"}, {assert.is_truthy("", "truthy")}) 554 | assert.is_same({nil, "falsy"}, {assert.is_falsy(nil, "falsy")}) 555 | assert.is_same({true, "boolean"}, {assert.is_boolean(true, "boolean")}) 556 | assert.is_same({false, "still boolean"}, {assert.is_boolean(false, "still boolean")}) 557 | assert.is_same({0, "is number"}, {assert.is_number(0, "is number")}) 558 | assert.is_same({"string", "is string"}, {assert.is_string("string", "is string")}) 559 | assert.is_same({{}, "empty table"}, {assert.is_table({}, "empty table")}) 560 | assert.is_same({nil, "string"}, {assert.is_nil(nil, "string")}) 561 | assert.is_same({{1, 2, 3}, "unique message"}, {assert.is_unique({1, 2, 3}, "unique message")}) 562 | assert.is_same({"foo", "foo", "bar"}, {assert.is_equal("foo", "foo", "bar")}) 563 | assert.is_same({"foo", "foo", "string"}, {assert.is_same("foo", "foo", "string")}) 564 | assert.is_same({0, 1, 2, "message"}, {assert.is_near(0, 1, 2, "message")}) 565 | end) 566 | 567 | it("Checks assert.has_match returns captures from match on success", function() 568 | assert.is_same({"string"}, {assert.has_match("(.*)", "string", "message")}) 569 | assert.is_same({"s", "n"}, {assert.has_match("(s).*(n)", "string", "message")}) 570 | assert.is_same({"tri"}, {assert.has_match("tri", "string", "message")}) 571 | assert.is_same({"ing"}, {assert.has_match("ing", "string", nil, true, "message")}) 572 | assert.is_same({}, {assert.has_no_match("%d+", "string", "message")}) 573 | assert.is_same({}, {assert.has_no_match("%d+", "string", nil, true, "message")}) 574 | end) 575 | 576 | it("Checks assert.has_error returns thrown error on success", function() 577 | assert.is_same({"err message", "err message"}, {assert.has_error(function() error("err message") end, "err message")}) 578 | assert.is_same({"err", "err"}, {assert.has_error(function() error(setmetatable({},{__tostring = function() return "err" end})) end, "err")}) 579 | assert.is_same({{}, {}}, {assert.has_error(function() error({}) end, {})}) 580 | -- Lua 5.3+ returns a non-string error 581 | assert.is_same({_VERSION < "Lua 5.3" and '0' or 0, 0}, {assert.has_error(function() error(0) end, 0)}) 582 | assert.is_same({nil, nil}, {assert.has_error(function() error(nil) end, nil)}) 583 | assert.is_same({nil, "string"}, {assert.has_no_error(function() end, "string")}) 584 | end) 585 | 586 | it("Checks assert.error_matches returns captures of thrown error on success", function() 587 | assert.is_same({"err", "message"}, {assert.error_matches(function() error("err message") end, "(err) (%w+)$")}) 588 | assert.is_same({"err"}, {assert.error_matches(function() error(setmetatable({},{__tostring = function() return "err" end})) end, "err", nil, true)}) 589 | assert.is_same({}, {assert.error_matches(function() error(nil) end, nil)}) 590 | end) 591 | 592 | it("Checks assert.no_error_matches returns thrown error on success", function() 593 | assert.is_same({nil, "string"}, {assert.no_error_matches(function() end, "string")}) 594 | assert.is_same({"error", "string"}, {assert.no_error_matches(function() error("error") end, "string")}) 595 | end) 596 | 597 | it("Checks 'array' modifier and 'holes' assertion", function() 598 | local arr = { true, true, true } 599 | assert.array(arr).has.no.holes() 600 | assert.array(arr).has.holes(4) 601 | assert.has.error(function() 602 | assert.array(arr).has.holes() 603 | end) 604 | assert.has.error(function() 605 | assert.has.holes() 606 | end) 607 | assert.has.error(function() 608 | assert.array(arr).array({}).has.holes() 609 | end) 610 | end) 611 | 612 | end) 613 | -------------------------------------------------------------------------------- /spec/formatters_spec.lua: -------------------------------------------------------------------------------- 1 | local util = require("luassert.util") 2 | 3 | local function returnnils() 4 | -- force the return of nils in an argument array 5 | return nil, nil, "this is not nil" 6 | end 7 | 8 | describe("Test Formatters", function() 9 | setup(function() 10 | end) 11 | 12 | local snapshot 13 | 14 | before_each(function() 15 | snapshot = assert:snapshot() 16 | end) 17 | 18 | after_each(function() 19 | snapshot:revert() 20 | end) 21 | 22 | it("Checks to see if types are returned as strings", function() 23 | assert.is.same(assert:format({ "a string", ["n"] = 1 })[1], "(string) 'a string'") 24 | assert.is.same(assert:format({ true, ["n"] = 1 })[1], "(boolean) true") 25 | assert.is.same(assert:format({ 1234, ["n"] = 1 })[1], "(number) 1234") 26 | assert.is.same(assert:format({ returnnils(), ["n"] = 3 })[1], "(nil)") 27 | local f = function() end 28 | local expected = tostring(f) 29 | assert.is.same(assert:format({ f, ["n"] = 1 })[1]:sub(1, #expected), expected) 30 | end) 31 | 32 | it("Checks to see if numbers are serialized correctly", function() 33 | assert.is.same(assert:format({ 1.0, ["n"] = 1 })[1], "(number) "..tostring(1.0)) 34 | assert.is.same(assert:format({ 23456789012E66, ["n"] = 1 })[1], "(number) 2.3456789012000000698e+76") 35 | assert.is.same(assert:format({ 0/0, ["n"] = 1 })[1], "(number) NaN") 36 | assert.is.same(assert:format({ 1/0, ["n"] = 1 })[1], "(number) Inf") 37 | assert.is.same(assert:format({ -1/0, ["n"] = 1 })[1], "(number) -Inf") 38 | end) 39 | 40 | it("Checks to see if tables are recursively serialized", function() 41 | local t = {} 42 | assert.is.same(assert:format({ t, ["n"] = 1 })[1], "("..tostring(t)..") { }") 43 | t = { 2, 3, 4, [-5] = 7} 44 | assert.is.same(assert:format({ t, ["n"] = 1 })[1], [[(]]..tostring(t)..[[) { 45 | [1] = 2 46 | [2] = 3 47 | [3] = 4 48 | [-5] = 7 }]]) 49 | t = { 1, ["k1"] = "v1", ["k2"] = "v2"} 50 | assert.is.same(assert:format({ t, ["n"] = 1 })[1], [[(]]..tostring(t)..[[) { 51 | [1] = 1 52 | [k1] = 'v1' 53 | [k2] = 'v2' }]]) 54 | t = { "{\n }\n" } 55 | assert.is.same(assert:format({ t, ["n"] = 1 })[1], [[(]]..tostring(t)..[[) { 56 | [1] = '{ 57 | } 58 | ' }]]) 59 | end) 60 | 61 | it("Checks to see if TableFormatLevel parameter limits table formatting depth", function() 62 | local t = { { { { 1 } } } } 63 | assert.is.same(assert:format({ t, ["n"] = 1 })[1], [[(]]..tostring(t)..[[) { 64 | [1] = { 65 | [1] = { 66 | [1] = { ... more } } } }]]) 67 | t = { { { } } } 68 | assert.is.same(assert:format({ t, ["n"] = 1 })[1], [[(]]..tostring(t)..[[) { 69 | [1] = { 70 | [1] = { } } }]]) 71 | assert:set_parameter("TableFormatLevel", 0) 72 | t = { } 73 | assert.is.same(assert:format({ t, ["n"] = 1 })[1], "("..tostring(t)..") { }") 74 | t = { 1 } 75 | assert.is.same(assert:format({ t, ["n"] = 1 })[1], "("..tostring(t)..") { ... more }") 76 | end) 77 | 78 | it("Checks to see if TableFormatLevel parameter can display all levels", function() 79 | assert:set_parameter("TableFormatLevel", -1) 80 | local t = { { { { 1 } } } } 81 | assert.is.same(assert:format({ t, ["n"] = 1 })[1], [[(]]..tostring(t)..[[) { 82 | [1] = { 83 | [1] = { 84 | [1] = { 85 | [1] = 1 } } } }]]) 86 | end) 87 | 88 | 89 | it("Checks to see if error character is applied only to key chain marked with crumbs", function() 90 | local old_color = assert:get_parameter("TableErrorHighlightColor") 91 | finally(function() 92 | assert:set_parameter("TableErrorHighlightColor", old_color) 93 | end) 94 | 95 | assert:set_parameter("TableErrorHighlightColor", "none") 96 | local t = {{1,2},{3,4}} 97 | local fmtargs = { {crumbs = {1,2}} } 98 | local formatted = assert:format({t, n = 1, fmtargs = fmtargs})[1] 99 | local expected = "("..tostring(t)..") {\n [1] = {\n [1] = 1\n [2] = 2 }\n *[2] = {\n *[1] = 3\n [2] = 4 } }" 100 | assert.is.equal(expected, formatted) 101 | end) 102 | 103 | it("Checks to see if TableErrorHighlightCharacter changes error character", function() 104 | local old_color = assert:get_parameter("TableErrorHighlightColor") 105 | local old_char = assert:get_parameter("TableErrorHighlightCharacter") 106 | finally(function() 107 | assert:set_parameter("TableErrorHighlightColor", old_color) 108 | assert:set_parameter("TableErrorHighlightCharacter", old_char) 109 | end) 110 | 111 | assert:set_parameter("TableErrorHighlightColor", "none") 112 | assert:set_parameter("TableErrorHighlightCharacter", "**") 113 | local t = {1,2,3} 114 | local fmtargs = { {crumbs = {2}} } 115 | local formatted = assert:format({t, n = 1, fmtargs = fmtargs})[1] 116 | local expected = "("..tostring(t)..") {\n [1] = 1\n**[2] = 2\n [3] = 3 }" 117 | assert.is.equal(expected, formatted) 118 | end) 119 | 120 | do 121 | local ok, colors = pcall(require, "term.colors") 122 | if not ok then 123 | pending("lua 'term.colors' module not available", function() end) 124 | else 125 | it("Checks to see if TableErrorHighlightColor changes error color", function() 126 | assert:set_parameter("TableErrorHighlightColor", "red") 127 | local t = {1,2,3} 128 | local fmtargs = { {crumbs = {2}} } 129 | local formatted = assert:format({t, n = 1, fmtargs = fmtargs})[1] 130 | local expected = string.format("("..tostring(t)..") {\n [1] = 1\n %s[2] = 2\n [3] = 3 }", colors.red("*")) 131 | assert.is.equal(expected, formatted) 132 | end) 133 | end 134 | end 135 | 136 | it("Checks to see if self referencing tables can be formatted", function() 137 | local t = {1,2} 138 | t[3] = t 139 | assert:set_parameter("TableFormatShowRecursion", true) 140 | local formatted = assert:format({t, n = 1})[1] 141 | local expected = "("..tostring(t)..") {\n [1] = 1\n [2] = 2\n [3] = { ... recursive } }" 142 | assert.is.equal(expected, formatted) 143 | end) 144 | 145 | it("Checks to see if table with 0 count is returned empty/0-count", function() 146 | local t = { ["n"] = 0 } 147 | local formatted = assert:format(t) 148 | assert.equals(type(formatted), "table") 149 | formatted.n = nil 150 | assert.equals(next(formatted), nil) 151 | end) 152 | 153 | it("Checks to see if empty table is returned empty", function() 154 | local t = {} 155 | local formatted = assert:format(t) 156 | assert.equals(type(formatted), "table") 157 | assert.equals(next(formatted), nil) 158 | end) 159 | 160 | it("Checks to see if table containing nils is returned with same number of entries #test", function() 161 | local t = { returnnils(), ["n"] = 3 } 162 | local formatted = assert:format(t) 163 | assert.is.same(type(formatted[1]), "string") 164 | assert.is.same(type(formatted[2]), "string") 165 | assert.is.same(type(formatted[3]), "string") 166 | assert.is.same(type(formatted[4]), "nil") 167 | end) 168 | 169 | it("checks matcher formatting", function() 170 | local pattern = match.match("pattern") 171 | local patternformatted = assert:format({ pattern, ["n"] = 1 })[1] 172 | assert.is.same("(matcher) is.match((string) 'pattern')", 173 | patternformatted) 174 | local nostring = match.no.string() 175 | local nostringformatted = assert:format({nostring, ["n"] = 1})[1] 176 | assert.is.same("(matcher) no.string()", 177 | nostringformatted) 178 | local anythingmatcherformatted = assert:format({match._, ["n"] = 1})[1] 179 | assert.is.same("(matcher) _ *anything*", anythingmatcherformatted) 180 | end) 181 | 182 | it("checks arglist formatting", function() 183 | local arglist = util.make_arglist("word", nil, 4, nil) 184 | local formatted = assert:format({ arglist, ["n"] = 1 })[1] 185 | assert.is.same(formatted, 186 | "(values list) ((string) 'word', (nil), (number) 4, (nil))") 187 | end) 188 | 189 | it("checks arguments not being formatted if set to do so", function() 190 | local arg1 = "argument1" 191 | local arg2 = "argument2" 192 | local arguments = { arg1, arg2 , ["n"] = 2} 193 | arguments.nofmt = { true } -- first arg not to be formatted 194 | arguments = assert:format(arguments) 195 | assert.is.same(arg1, arguments[1]) 196 | end) 197 | 198 | it("checks extra formatters inserted to be called first", function() 199 | local expected = "formatted result" 200 | local f = function(value) 201 | if type(value) == "string" then 202 | return expected 203 | end 204 | end 205 | local s = spy.new(f) 206 | 207 | assert:add_formatter(s) 208 | assert.are_equal(expected, assert:format({"some string"})[1]) 209 | assert.spy(s).was.called(1) 210 | assert:remove_formatter(s) 211 | end) 212 | 213 | end) 214 | -------------------------------------------------------------------------------- /spec/helper.lua: -------------------------------------------------------------------------------- 1 | -- busted helper file to prevent crashes on LuaJIT ffi module being 2 | -- garbage collected due to Busted cleaning up the test environment 3 | -- 4 | -- usage: 5 | -- busted --helper=spec/helper.lua 6 | 7 | -- only apply when we're running LuaJIT 8 | local isJit = (tostring(assert):match('builtin') ~= nil) 9 | 10 | if isJit then 11 | -- pre-load the ffi module, such that it becomes part of the environment 12 | -- and Busted will not try to GC and reload it. The ffi is not suited 13 | -- for that and will occasionally segfault if done so. 14 | local ffi = require "ffi" 15 | 16 | -- Now patch ffi.cdef to only be called once with each definition, as it 17 | -- will error on re-registering. 18 | local old_cdef = ffi.cdef 19 | local exists = {} 20 | ffi.cdef = function(def) 21 | if exists[def] then return end 22 | exists[def] = true 23 | return old_cdef(def) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/matchers_spec.lua: -------------------------------------------------------------------------------- 1 | local match = require 'luassert.match' 2 | 3 | assert(type(match) == "table") 4 | 5 | describe("Test Matchers", function() 6 | it("Checks wildcard() matcher", function() 7 | assert.is_true(match._(nil)) 8 | assert.is_true(match._(true)) 9 | assert.is_true(match._(false)) 10 | assert.is_true(match._(123)) 11 | assert.is_true(match._("")) 12 | assert.is_true(match._({})) 13 | assert.is_true(match._(function() end)) 14 | end) 15 | 16 | it("Checks truthy() matcher", function() 17 | assert.is_false(match.truthy()(nil)) 18 | assert.is_true(match.truthy()(true)) 19 | assert.is_false(match.truthy()(false)) 20 | assert.is_true(match.truthy()(123)) 21 | assert.is_true(match.truthy()("")) 22 | assert.is_true(match.truthy()({})) 23 | assert.is_true(match.truthy()(function() end)) 24 | end) 25 | 26 | it("Checks falsy() matcher", function() 27 | assert.is_true(match.falsy()(nil)) 28 | assert.is_false(match.falsy()(true)) 29 | assert.is_true(match.falsy()(false)) 30 | assert.is_false(match.falsy()(123)) 31 | assert.is_false(match.falsy()("")) 32 | assert.is_false(match.falsy()({})) 33 | assert.is_false(match.falsy()(function() end)) 34 | end) 35 | 36 | it("Checks true() matcher", function() 37 | assert.is_false(match.is_true()(nil)) 38 | assert.is_true(match.is_true()(true)) 39 | assert.is_false(match.is_true()(false)) 40 | assert.is_false(match.is_true()(123)) 41 | assert.is_false(match.is_true()("")) 42 | assert.is_false(match.is_true()({})) 43 | assert.is_false(match.is_true()(function() end)) 44 | end) 45 | 46 | it("Checks false() matcher", function() 47 | assert.is_false(match.is_false()(nil)) 48 | assert.is_false(match.is_false()(true)) 49 | assert.is_true(match.is_false()(false)) 50 | assert.is_false(match.is_false()(123)) 51 | assert.is_false(match.is_false()("")) 52 | assert.is_false(match.is_false()({})) 53 | assert.is_false(match.is_false()(function() end)) 54 | end) 55 | 56 | it("Checks nil() matcher", function() 57 | assert.is_true(match.is_nil()(nil)) 58 | assert.is_false(match.is_nil()(true)) 59 | assert.is_false(match.is_nil()(false)) 60 | assert.is_false(match.is_nil()(123)) 61 | assert.is_false(match.is_nil()("")) 62 | assert.is_false(match.is_nil()({})) 63 | assert.is_false(match.is_nil()(function() end)) 64 | end) 65 | 66 | it("Checks boolean() matcher", function() 67 | assert.is_false(match.is_boolean()(nil)) 68 | assert.is_true(match.is_boolean()(true)) 69 | assert.is_true(match.is_boolean()(false)) 70 | assert.is_false(match.is_boolean()(123)) 71 | assert.is_false(match.is_boolean()("")) 72 | assert.is_false(match.is_boolean()({})) 73 | assert.is_false(match.is_boolean()(function() end)) 74 | end) 75 | 76 | it("Checks number() matcher", function() 77 | assert.is_false(match.is_number()(nil)) 78 | assert.is_false(match.is_number()(true)) 79 | assert.is_false(match.is_number()(false)) 80 | assert.is_true(match.is_number()(123)) 81 | assert.is_false(match.is_number()("")) 82 | assert.is_false(match.is_number()({})) 83 | assert.is_false(match.is_number()(function() end)) 84 | end) 85 | 86 | it("Checks string() matcher", function() 87 | assert.is_false(match.is_string()(nil)) 88 | assert.is_false(match.is_string()(true)) 89 | assert.is_false(match.is_string()(false)) 90 | assert.is_false(match.is_string()(123)) 91 | assert.is_true(match.is_string()("")) 92 | assert.is_false(match.is_string()({})) 93 | assert.is_false(match.is_string()(function() end)) 94 | end) 95 | 96 | it("Checks table() matcher", function() 97 | assert.is_false(match.is_boolean()(nil)) 98 | assert.is_false(match.is_table()(nil)) 99 | assert.is_false(match.is_table()(true)) 100 | assert.is_false(match.is_table()(false)) 101 | assert.is_false(match.is_table()(123)) 102 | assert.is_false(match.is_table()("")) 103 | assert.is_true(match.is_table()({})) 104 | assert.is_false(match.is_table()(function() end)) 105 | end) 106 | 107 | it("Checks function() matcher", function() 108 | assert.is_false(match.is_function()(nil)) 109 | assert.is_false(match.is_function()(true)) 110 | assert.is_false(match.is_function()(false)) 111 | assert.is_false(match.is_function()(123)) 112 | assert.is_false(match.is_function()("")) 113 | assert.is_false(match.is_function()({})) 114 | assert.is_true(match.is_function()(function() end)) 115 | end) 116 | 117 | it("Checks userdata() matcher", function() 118 | assert.is_true(match.is_userdata()(io.stdout)) 119 | assert.is_false(match.is_userdata()(nil)) 120 | assert.is_false(match.is_userdata()(true)) 121 | assert.is_false(match.is_userdata()(false)) 122 | assert.is_false(match.is_userdata()(123)) 123 | assert.is_false(match.is_userdata()("")) 124 | assert.is_false(match.is_userdata()({})) 125 | assert.is_false(match.is_userdata()(function() end)) 126 | end) 127 | 128 | it("Checks thread() matcher", function() 129 | local mythread = coroutine.create(function() end) 130 | assert.is_true(match.is_thread()(mythread)) 131 | assert.is_false(match.is_thread()(nil)) 132 | assert.is_false(match.is_thread()(true)) 133 | assert.is_false(match.is_thread()(false)) 134 | assert.is_false(match.is_thread()(123)) 135 | assert.is_false(match.is_thread()("")) 136 | assert.is_false(match.is_thread()({})) 137 | assert.is_false(match.is_thread()(function() end)) 138 | end) 139 | 140 | it("Checks to see if tables 1 and 2 are equal", function() 141 | local table1 = { derp = false} 142 | local table2 = table1 143 | assert.is_true(match.is_equal(table1)(table2)) 144 | assert.is_true(match.is_equal(table2)(table1)) 145 | end) 146 | 147 | it("Checks equals() matcher to handle nils properly", function() 148 | assert.is.error(function() match.is_equals() end) -- minimum 1 argument 149 | assert.is_true(match.is_equal(nil)(nil)) 150 | assert.is_false(match.is_equal("a string")(nil)) 151 | assert.is_false(match.is_equal(nil)("a string")) 152 | end) 153 | 154 | it("Checks the same() matcher for tables with protected metatables", function() 155 | local troubleSomeTable = {} 156 | setmetatable(troubleSomeTable, {__metatable = 0}) 157 | assert.is_true(match.is_same(troubleSomeTable)(troubleSomeTable)) 158 | end) 159 | 160 | it("Checks same() matcher to handle nils properly", function() 161 | assert.is.error(function() match.same()() end) -- minimum 1 arguments 162 | assert.is_true(match.is_same(nil)(nil)) 163 | assert.is_false(match.is_same("a string")(nil)) 164 | assert.is_false(match.is_same(nil)("a string")) 165 | end) 166 | 167 | it("Checks ref() matcher", function() 168 | local t = {} 169 | local func = function() end 170 | local mythread = coroutine.create(func) 171 | assert.is.error(function() match.is_ref() end) -- minimum 1 arguments 172 | assert.is.error(function() match.is_ref(0) end) -- arg1 must be an object 173 | assert.is.error(function() match.is_ref('') end) -- arg1 must be an object 174 | assert.is.error(function() match.is_ref(nil) end) -- arg1 must be an object 175 | assert.is.error(function() match.is_ref(true) end) -- arg1 must be an object 176 | assert.is.error(function() match.is_ref(false) end) -- arg1 must be an object 177 | assert.is_true(match.is_ref(t)(t)) 178 | assert.is_true(match.is_ref(func)(func)) 179 | assert.is_true(match.is_ref(mythread)(mythread)) 180 | assert.is_false(match.is_ref(t)(func)) 181 | assert.is_false(match.is_ref(t)(mythread)) 182 | assert.is_false(match.is_ref(t)(nil)) 183 | assert.is_false(match.is_ref(t)(true)) 184 | assert.is_false(match.is_ref(t)(false)) 185 | assert.is_false(match.is_ref(t)(123)) 186 | assert.is_false(match.is_ref(t)("")) 187 | assert.is_false(match.is_ref(t)({})) 188 | assert.is_false(match.is_ref(t)(function() end)) 189 | end) 190 | 191 | it("Checks matches() matcher does string matching", function() 192 | assert.is.error(function() match.matches() end) -- minimum 1 arguments 193 | assert.is.error(function() match.matches({}) end) -- arg1 must be a string 194 | assert.is.error(function() match.matches('s', 's') end) -- arg2 must be a number or nil 195 | assert.is_true(match.matches("%w+")("test")) 196 | assert.is_true(match.has.match("%w+")("test")) 197 | assert.is_false(match.matches("%d+")("derp")) 198 | assert.is_true(match.has_match("test", nil, true)("test")) 199 | assert.is_false(match.has_match("%w+", nil, true)("test")) 200 | assert.is_true(match.has_match("^test", 5)("123 test")) 201 | assert.is_false(match.has_match("%d+", '4')("123 test")) 202 | end) 203 | 204 | it("Checks near() matcher handles tolerances", function() 205 | assert.is.error(function() match.near(0) end) -- minimum 2 arguments 206 | assert.is.error(function() match.near('a', 0) end) -- arg1 must be convertable to number 207 | assert.is.error(function() match.near(0, 'a') end) -- arg2 must be convertable to number 208 | assert.is_true(match.is.near(1.5, 0.5)(2.0)) 209 | assert.is_true(match.is.near('1.5', '0.5')('2.0')) 210 | assert.is_true(match.is_not.near(1.5, 0.499)(2.0)) 211 | assert.is_true(match.is_not.near('1.5', '0.499')('2.0')) 212 | end) 213 | 214 | it("Checks to see if table1 only contains unique elements", function() 215 | local table2 = { derp = false} 216 | local table3 = { derp = true } 217 | local table1 = {table2,table3} 218 | local tablenotunique = {table2,table2} 219 | assert.is_true(match.is.unique()(table1)) 220 | assert.is_true(match.is_not.unique()(tablenotunique)) 221 | end) 222 | 223 | it("Checks '_' chaining of modifiers and match", function() 224 | assert.is_true(match.is_string()("abc")) 225 | assert.is_true(match.is_true()(true)) 226 | assert.is_true(match.is_not_string()(123)) 227 | assert.is_true(match.is_nil()(nil)) 228 | assert.is_true(match.is_not_nil()({})) 229 | assert.is_true(match.is_not_true()(false)) 230 | assert.is_true(match.is_not_false()(true)) 231 | 232 | -- verify that failing match return false 233 | assert.is_false(match.is_string()(1)) 234 | assert.is_false(match.is_true()(false)) 235 | assert.is_false(match.is_not_string()('string!')) 236 | assert.is_false(match.is_nil()({})) 237 | assert.is_false(match.is_not_nil()(nil)) 238 | assert.is_false(match.is_not_true()(true)) 239 | assert.is_false(match.is_not_false()(false)) 240 | end) 241 | 242 | it("Checks '.' chaining of modifiers and match", function() 243 | assert.is_true(match.is.string()("abc")) 244 | assert.is_true(match.is.True()(true)) 245 | assert.is_true(match.is.Not.string()(123)) 246 | assert.is_true(match.is.Nil()(nil)) 247 | assert.is_true(match.is.Not.Nil()({})) 248 | assert.is_true(match.is.Not.True()(false)) 249 | assert.is_true(match.is.Not.False()(true)) 250 | assert.is_true(match.equals.Not(true)(false)) 251 | assert.is_true(match.equals.Not.Not(true)(true)) 252 | assert.is_true(match.Not.equals.Not(true)(true)) 253 | 254 | -- verify that failing match return false 255 | assert.is_false(match.is.string()(1)) 256 | assert.is_false(match.is.True()(false)) 257 | assert.is_false(match.is.Not.string()('string!')) 258 | assert.is_false(match.is.Nil()({})) 259 | assert.is_false(match.is.Not.Nil()(nil)) 260 | assert.is_false(match.is.Not.True()(true)) 261 | assert.is_false(match.is.Not.False()(false)) 262 | assert.is_false(match.equals.Not(true)(true)) 263 | assert.is_false(match.equals.Not.Not(true)(false)) 264 | assert.is_false(match.Not.equals.Not(true)(false)) 265 | end) 266 | 267 | it("Checks called_with() argument matching for spies", function() 268 | local s = spy.new(function() return "foo" end) 269 | s(1) 270 | s(nil, "") 271 | s({}, "") 272 | s(function() end, "") 273 | s(1, 2, 3) 274 | s("a", "b", "c", "d") 275 | assert.spy(s).was.called_with(match._) 276 | assert.spy(s).was.called_with(match.is_number()) 277 | assert.spy(s).was.called_with(match.is_number(), match.is_number(), match.is_number()) 278 | assert.spy(s).was_not.called_with(match.is_string()) 279 | assert.spy(s).was.called_with(match.is_string(), match.is_string(), match.is_string(), match.is_string()) 280 | assert.spy(s).was.called_with(match.is_nil(), match._) 281 | assert.spy(s).was.called_with(match.is_table(), match._) 282 | assert.spy(s).was.called_with(match.is_function(), match._) 283 | assert.spy(s).was_not.called_with(match.is_nil()) 284 | assert.spy(s).was_not.called_with(match.is_table()) 285 | assert.spy(s).was_not.called_with(match.is_function()) 286 | end) 287 | 288 | it("Checks returned_with() argument matching for spies", function() 289 | local s = spy.new(function() return "foo" end) 290 | s() 291 | assert.spy(s).was.returned_with(match._) 292 | assert.spy(s).was.returned_with(match.is_string()) 293 | assert.spy(s).was.returned_with(match.is_not_number()) 294 | assert.spy(s).was.returned_with(match.is_not_table()) 295 | assert.spy(s).was_not.returned_with(match.is_number()) 296 | assert.spy(s).was_not.returned_with(match.is_table()) 297 | end) 298 | 299 | it("Checks on_call_with() argument matching for stubs", function() 300 | local test = {} 301 | local s = stub(test, "key").returns("foo") 302 | s.on_call_with(match.is_string()).returns("bar") 303 | s.on_call_with(match.is_number()).returns(555) 304 | s.on_call_with(match.is_table()).returns({"foo"}) 305 | s(0) 306 | s("") 307 | s({}) 308 | assert.spy(s).was.returned_with(555) 309 | assert.spy(s).was.returned_with("bar") 310 | assert.spy(s).was.returned_with({"foo"}) 311 | end) 312 | 313 | it("Checks returned_with() argument matching for spies", function() 314 | local s = spy.new(function() return "foo" end) 315 | s() 316 | assert.spy(s).was.returned_with(match._) 317 | assert.spy(s).was.returned_with(match.is_string()) 318 | assert.spy(s).was.returned_with(match.is_not_nil()) 319 | assert.spy(s).was.returned_with(match.is_not_number()) 320 | assert.spy(s).was.returned_with(match.is_not_table()) 321 | assert.spy(s).was.returned_with(match.is_not_function()) 322 | end) 323 | 324 | it("Checks none() composite matcher", function() 325 | assert.has.error(function() match.none_of() end) -- minimum 1 arguments 326 | assert.has.error(function() match.none_of('') end) -- arg must be a matcher 327 | assert.has.error(function() match.none_of('', 0) end) -- all args must be a match 328 | 329 | assert.is_false(match.none_of(match.is_string())('')) 330 | assert.is_true(match.none_of(match.is_number())('')) 331 | assert.is_true(match.none_of(match.is_number(), match.is_function())('')) 332 | assert.is_false(match.none_of(match.is_number(), match.is_not_function())('')) 333 | assert.is_false(match.not_none_of(match.is_number(), match.is_function())('')) 334 | end) 335 | 336 | it("Checks any() composite matcher", function() 337 | assert.has.error(function() match.any_of() end) -- minimum 1 arguments 338 | assert.has.error(function() match.any_of('') end) -- arg must be a matcher 339 | assert.has.error(function() match.any_of('', 0) end) -- all args must be a match 340 | 341 | assert.is_true(match.any_of(match.is_string())('')) 342 | assert.is_false(match.any_of(match.is_number())('')) 343 | assert.is_false(match.any_of(match.is_number(), match.is_function())('')) 344 | assert.is_true(match.any_of(match.is_number(), match.is_not_function())('')) 345 | assert.is_true(match.not_any_of(match.is_number(), match.is_function())('')) 346 | end) 347 | 348 | it("Checks all() composite matcher", function() 349 | assert.has.error(function() match.all_of() end) -- minimum 1 arguments 350 | assert.has.error(function() match.all_of('') end) -- arg must be a matcher 351 | assert.has.error(function() match.all_of('', 0) end) -- all args must be a match 352 | 353 | assert.is_true(match.all_of(match.is_string())('')) 354 | assert.is_false(match.all_of(match.is_number())('')) 355 | assert.is_false(match.all_of(match.is_number(), match.is_function())('')) 356 | assert.is_false(match.all_of(match.is_number(), match.is_not_function())('')) 357 | assert.is_true(match.not_all_of(match.is_number(), match.is_function())('')) 358 | assert.is_true(match.all_of(match.is_not_number(), match.is_not_function())('')) 359 | end) 360 | 361 | end) 362 | -------------------------------------------------------------------------------- /spec/mocks_spec.lua: -------------------------------------------------------------------------------- 1 | describe("Tests dealing with mocks", function() 2 | local test = {} 3 | 4 | before_each(function() 5 | test = { 6 | key = function() 7 | return "derp" 8 | end 9 | } 10 | end) 11 | 12 | it("makes sure we're returning the same table", function() 13 | local val = tostring(test) 14 | assert(type(mock(test)) == "table") 15 | assert(tostring(mock(test)) == val) 16 | end) 17 | 18 | it("makes sure function calls are spies", function() 19 | assert(type(test.key) == "function") 20 | mock(test) 21 | assert(type(test.key) == "table") 22 | assert(test.key() == "derp") 23 | end) 24 | 25 | it("makes sure function calls are stubs when specified", function() 26 | assert(type(test.key) == "function") 27 | mock(test, true) 28 | assert(type(test.key) == "table") 29 | assert(test.key() == nil) 30 | end) 31 | 32 | it("makes sure call history can be cleared", function() 33 | test.foo = { bar = function() return "foobar" end } 34 | mock(test) 35 | test.key() 36 | test.key("test") 37 | test.foo.bar() 38 | test.foo.bar("hello world") 39 | assert.spy(test.key).was.called() 40 | assert.spy(test.foo.bar).was.called() 41 | mock.clear(test) 42 | assert.spy(test.key).was_not.called() 43 | assert.spy(test.foo.bar).was_not.called() 44 | end) 45 | 46 | it("makes sure table can be reverted to pre-mock state", function() 47 | local val = tostring(test) 48 | mock(test) 49 | mock.revert(test) 50 | assert(type(test.key) == "function") 51 | assert(test.key() == "derp") 52 | assert(tostring(test) == val) 53 | end) 54 | 55 | it("makes sure cycles are detected", function() 56 | test.foo = test 57 | mock(test) 58 | assert(test.foo == test) 59 | end) 60 | 61 | it("should try to show why called_with fails", function() 62 | mock(test) 63 | test.key() 64 | assert.error_matches( 65 | function () assert.spy(test.key).was.called_with(5) end, 66 | "Function was never called with matching arguments.\n" 67 | .. "Called with (last call if any):\n" 68 | .. "(values list) ()\n" 69 | .. "Expected:\n" 70 | .. "(values list) ((number) 5)", 71 | 1, true) 72 | end) 73 | 74 | it("should try to show why returned_with fails", function() 75 | mock(test) 76 | test.key() 77 | assert.error_matches( 78 | function () assert.spy(test.key).returned_with(5) end, 79 | "Function never returned matching arguments.\n" 80 | .. "Returned (last call if any):\n" 81 | .. "(values list) ((string) 'derp')\n" 82 | .. "Expected:\n" 83 | .. "(values list) ((number) 5)", 84 | 1, true) 85 | end) 86 | end) 87 | -------------------------------------------------------------------------------- /spec/spies_spec.lua: -------------------------------------------------------------------------------- 1 | local match = require 'luassert.match' 2 | 3 | describe("Tests dealing with spies", function() 4 | local test = {} 5 | 6 | before_each(function() 7 | assert:set_parameter("TableFormatLevel", 3) 8 | test = {key = function() 9 | return "derp" 10 | end} 11 | end) 12 | 13 | it("checks if a spy actually executes the internal function", function() 14 | spy.on(test, "key") 15 | assert(test.key() == "derp") 16 | end) 17 | 18 | it("checks to see if spy keeps track of arguments", function() 19 | spy.on(test, "key") 20 | 21 | test.key("derp") 22 | assert.spy(test.key).was.called_with("derp") 23 | assert.errors(function() assert.spy(test.key).was.called_with("herp") end) 24 | end) 25 | 26 | it("checks to see if spy keeps track of returned arguments", function() 27 | spy.on(test, "key") 28 | 29 | test.key() 30 | assert.spy(test.key).was.returned_with("derp") 31 | assert.errors(function() assert.spy(test.key).was.returned_with("herp") end) 32 | end) 33 | 34 | it("checks to see if spy keeps track of number of calls", function() 35 | spy.on(test, "key") 36 | test.key() 37 | test.key("test") 38 | assert.spy(test.key).was.called(2) 39 | end) 40 | 41 | it("checks returned_with() assertions", function() 42 | local s = spy.new(function(...) return ... end) 43 | local t = { foo = { bar = { "test" } } } 44 | local _ = match._ 45 | 46 | s(1, 2, 3) 47 | s("a", "b", "c") 48 | s(t) 49 | t.foo.bar = "value" 50 | 51 | assert.spy(s).was.returned_with(1, 2, 3) 52 | assert.spy(s).was_not.returned_with({1, 2, 3}) -- mind the accolades 53 | assert.spy(s).was.returned_with(_, 2, 3) -- matches don't care 54 | assert.spy(s).was.returned_with(_, _, _) -- matches multiple don't cares 55 | assert.spy(s).was_not.returned_with(_, _, _, _) -- does not match if too many args 56 | assert.spy(s).was.returned_with({ foo = { bar = { "test" } } }) -- matches original table 57 | assert.spy(s).was_not.returned_with(t) -- does not match modified table 58 | assert.error_matches( 59 | function() assert.spy(s).returned_with(5, 6) end, 60 | "Function never returned matching arguments.\n" 61 | .. "Returned %(last call if any%):\n" 62 | .. "%(values list%) %(%(table: 0x%x+%) {\n" 63 | .. " %[foo%] = {\n" 64 | .. " %[bar%] = {\n" 65 | .. " %[1%] = 'test' } } }.\n" 66 | .. "Expected:\n" 67 | .. "%(values list%) %(%(number%) 5, %(number%) 6%)") 68 | -- assertion can format the `_` anything matcher appropriately in the fail message 69 | assert.error_matches( 70 | function() assert.spy(s).returned_with(5, _) end, 71 | "Function never returned matching arguments.\n" 72 | .. "Returned %(last call if any%):\n" 73 | .. "%(values list%) %(%(table: 0x%x+%) {\n" 74 | .. " %[foo%] = {\n" 75 | .. " %[bar%] = {\n" 76 | .. " %[1%] = 'test' } } }.\n" 77 | .. "Expected:\n" 78 | .. "%(values list%) %(%(number%) 5, %(matcher%) _ %*anything%*%)") 79 | end) -- (matcher) _ *anything* 80 | 81 | it("checks called() and called_with() assertions", function() 82 | local s = spy.new(function() end) 83 | local t = { foo = { bar = { "test" } } } 84 | local _ = match._ 85 | 86 | s(1, 2, 3) 87 | s("a", "b", "c") 88 | s(t) 89 | t.foo.bar = "value" 90 | 91 | assert.spy(s).was.called() 92 | assert.spy(s).was.called(3) -- 3 times! 93 | assert.spy(s).was_not.called(4) 94 | assert.spy(s).was_not.called_with({1, 2, 3}) -- mind the accolades 95 | assert.spy(s).was.called_with(1, 2, 3) 96 | assert.spy(s).was.called_with(_, 2, 3) -- matches don't care 97 | assert.spy(s).was.called_with(_, _, _) -- matches multiple don't cares 98 | assert.spy(s).was_not.called_with(_, _, _, _) -- does not match if too many args 99 | assert.spy(s).was.called_with({ foo = { bar = { "test" } } }) -- matches original table 100 | assert.spy(s).was_not.called_with(t) -- does not match modified table 101 | assert.error_matches( 102 | function() assert.spy(s).was.called_with(5, 6) end, 103 | "Function was never called with matching arguments.\n" 104 | .. "Called with %(last call if any%):\n" 105 | .. "%(values list%) %(%(table: 0x%x+%) {\n" 106 | .. " %[foo%] = {\n" 107 | .. " %[bar%] = {\n" 108 | .. " %[1%] = 'test' } } }%)\n" 109 | .. "Expected:\n" 110 | .. "%(values list%) %(%(number%) 5, %(number%) 6%)") 111 | -- assertion can format the `_` anything matcher appropriately in the fail message 112 | assert.error_matches( 113 | function() assert.spy(s).was.called_with(5, _) end, 114 | "Function was never called with matching arguments.\n" 115 | .. "Called with %(last call if any%):\n" 116 | .. "%(values list%) %(%(table: 0x%x+%) {\n" 117 | .. " %[foo%] = {\n" 118 | .. " %[bar%] = {\n" 119 | .. " %[1%] = 'test' } } }%)\n" 120 | .. "Expected:\n" 121 | .. "%(values list%) %(%(number%) 5, %(matcher%) _ %*anything%*%)") 122 | end) 123 | 124 | it("checks called() and called_with() assertions using refs", function() 125 | local s = spy.new(function() end) 126 | local t1 = { foo = { bar = { "test" } } } 127 | local t2 = { foo = { bar = { "test" } } } 128 | 129 | s(t1) 130 | t1.foo.bar = "value" 131 | 132 | assert.spy(s).was.called_with(t2) 133 | assert.spy(s).was_not.called_with(match.is_ref(t2)) 134 | assert.spy(s).was.called_with(match.is_ref(t1)) 135 | end) 136 | 137 | it("checks called_with(aspy) assertions", function() 138 | local s = spy.new(function() end) 139 | 140 | s(s) 141 | 142 | assert.spy(s).was.called_with(s) 143 | end) 144 | 145 | it("checks called_at_least() assertions", function() 146 | local s = spy.new(function() end) 147 | 148 | s(1, 2, 3) 149 | s("a", "b", "c") 150 | assert.spy(s).was.called.at_least(1) 151 | assert.spy(s).was.called.at_least(2) 152 | assert.spy(s).was_not.called.at_least(3) 153 | assert.error_matches( 154 | function() assert.spy(s).was.called.at_least() end, 155 | "attempt to compare %a+ with %a+") -- nil with number, order non-deterministic 156 | end) 157 | 158 | it("checks called_at_most() assertions", function() 159 | local s = spy.new(function() end) 160 | 161 | s(1, 2, 3) 162 | s("a", "b", "c") 163 | assert.spy(s).was.called.at_most(3) 164 | assert.spy(s).was.called.at_most(2) 165 | assert.spy(s).was_not.called.at_most(1) 166 | assert.error_matches( 167 | function() assert.spy(s).was.called.at_most() end, 168 | "attempt to compare %a+ with %a+") -- nil with number, order non-deterministic 169 | end) 170 | 171 | it("checks called_more_than() assertions", function() 172 | local s = spy.new(function() end) 173 | 174 | s(1, 2, 3) 175 | s("a", "b", "c") 176 | assert.spy(s).was.called.more_than(0) 177 | assert.spy(s).was.called.more_than(1) 178 | assert.spy(s).was_not.called.more_than(2) 179 | assert.error_matches( 180 | function() assert.spy(s).was.called.more_than() end, 181 | "attempt to compare nil with number") 182 | end) 183 | 184 | it("checks called_less_than() assertions", function() 185 | local s = spy.new(function() end) 186 | 187 | s(1, 2, 3) 188 | s("a", "b", "c") 189 | assert.spy(s).was.called.less_than(4) 190 | assert.spy(s).was.called.less_than(3) 191 | assert.spy(s).was_not.called.less_than(2) 192 | assert.error_matches( 193 | function() assert.spy(s).was.called.less_than() end, 194 | "attempt to compare number with nil") 195 | end) 196 | 197 | it("checks if called()/called_with assertions fail on non-spies ", function() 198 | assert.has_error(assert.was.called) 199 | assert.has_error(assert.was.called_at_least) 200 | assert.has_error(assert.was.called_at_most) 201 | assert.has_error(assert.was.called_more_than) 202 | assert.has_error(assert.was.called_less_than) 203 | assert.has_error(assert.was.called_with) 204 | assert.has_error(assert.was.returned_with) 205 | end) 206 | 207 | it("checks spies to fail when spying on non-callable elements", function() 208 | local s 209 | local testfunc = function() 210 | spy.new(s) 211 | end 212 | -- try some types to fail 213 | s = "some string"; assert.has_error(testfunc) 214 | s = 10; assert.has_error(testfunc) 215 | s = true; assert.has_error(testfunc) 216 | -- try some types to succeed 217 | s = function() end; assert.has_no_error(testfunc) 218 | s = setmetatable( {}, { __call = function() end } ); assert.has_no_error(testfunc) 219 | end) 220 | 221 | it("checks reverting a spy.on call", function() 222 | local old = test.key 223 | local s = spy.on(test, "key") 224 | test.key() 225 | test.key("test") 226 | assert.spy(test.key).was.called(2) 227 | -- revert and call again 228 | s:revert() 229 | assert.are.equal(old, test.key) 230 | test.key() 231 | test.key("test") 232 | assert.spy(s).was.called(2) -- still two, spy was removed 233 | end) 234 | 235 | it("checks reverting a spy.new call", function() 236 | local calls = 0 237 | local old = function() calls = calls + 1 end 238 | local s = spy.new(old) 239 | assert.is_table(s) 240 | s() 241 | s() 242 | assert.spy(s).was.called(2) 243 | assert.are.equal(calls, 2) 244 | local old_s = s 245 | s = s:revert() 246 | assert.are.equal(s, old) 247 | s() 248 | assert.spy(old_s).was.called(2) -- still two, spy was removed 249 | assert.are.equal(calls, 3) 250 | end) 251 | 252 | it("checks clearing a spy.on call history", function() 253 | local s = spy.on(test, "key") 254 | test.key() 255 | test.key("test") 256 | s:clear() 257 | assert.spy(s).was.called(0) 258 | end) 259 | 260 | it("checks clearing a spy.new call history", function() 261 | local s = spy.new(function() return "foobar" end) 262 | s() 263 | s("test") 264 | s:clear() 265 | assert.spy(s).was.called(0) 266 | assert.spy(s).was_not.returned_with("foobar") 267 | end) 268 | 269 | it("checks spy.new can be constructed without arguments", function() 270 | local s = spy.new() 271 | s() 272 | assert.spy(s).was.called(1) 273 | end) 274 | 275 | it("reports some argumentslist the spy was called_with when none matches", function() 276 | local s = spy.new(function() end) 277 | s("herp", nil, "bust", nil) 278 | assert.error_matches( 279 | function() assert.spy(s).was.called_with() end, 280 | "Function was never called with matching arguments.\n" 281 | .. "Called with (last call if any):\n" 282 | .. "(values list) ((string) 'herp', (nil), (string) 'bust', (nil))\n" 283 | .. "Expected:\n" 284 | .. "(values list) ()", 285 | 1, true) 286 | end) 287 | 288 | it("reports some matching call argumentslist when none should match", function() 289 | assert:set_parameter("TableFormatLevel", 4) 290 | local s = spy.new(function() end) 291 | s({}, nil, {}, nil) 292 | s("herp", nil, "bust", nil) 293 | s({}, nil, {}, nil) 294 | assert.error_matches( 295 | function() 296 | assert.spy(s).was_not.called_with(match.match("er"), nil, match.string(), nil) 297 | end, 298 | "Function was called with matching arguments at least once.\n" 299 | .. "Called with (last matching call):\n" 300 | .. "(values list) ((string) 'herp', (nil), (string) 'bust', (nil))\n" 301 | .. "Did not expect:\n" 302 | .. "(values list) ((matcher) is.match((string) 'er'), (nil), (matcher) is.string(), (nil))", 303 | 1, true) 304 | end) 305 | 306 | it("makes legible errors when never called", function() 307 | local s = spy.new(function() end) 308 | assert.error_matches( 309 | function() assert.spy(s).was.called_with("derp", nil, "bust", nil) end, 310 | "Function was never called with matching arguments.\n" 311 | .. "Called with (last call if any):\n" 312 | .. "(nil)\n" 313 | .. "Expected:\n" 314 | .. "(values list) ((string) 'derp', (nil), (string) 'bust', (nil))", 315 | 1, true) 316 | end) 317 | 318 | it("reports some return values from the spy when none mathes", function() 319 | local s = spy.new(function(...) return ... end) 320 | s("herp", nil, "bust", nil) 321 | assert.error_matches( 322 | function() assert.spy(s).returned_with("derp", nil, "bust", nil) end, 323 | "Function never returned matching arguments.\n" 324 | .. "Returned (last call if any):\n" 325 | .. "(values list) ((string) 'herp', (nil), (string) 'bust', (nil))\n" 326 | .. "Expected:\n" 327 | .. "(values list) ((string) 'derp', (nil), (string) 'bust', (nil))", 328 | 1, true) 329 | end) 330 | 331 | it("reports some matching return values when none should match", function() 332 | assert:set_parameter("TableFormatLevel", 4) 333 | local s = spy.new(function(...) return ... end) 334 | s({}, nil, {}, nil) 335 | s("herp", nil, "bust", nil) 336 | s({}, nil, {}, nil) 337 | assert.error_matches( 338 | function() 339 | assert.spy(s).has_not.returned_with(match.matches("er"), nil, match.is_string(), nil) 340 | end, 341 | "Function returned matching arguments at least once.\n" 342 | .. "Returned (last matching call):\n" 343 | .. "(values list) ((string) 'herp', (nil), (string) 'bust', (nil))", 344 | 1, true) 345 | end) 346 | 347 | it("makes legible errors when never returned", function() 348 | local s = spy.new(function(...) return ... end) 349 | assert.error_matches( 350 | function() assert.spy(s).returned_with() end, 351 | "Function never returned matching arguments.\n" 352 | .. "Returned (last call if any):\n" 353 | .. "(nil)\n" 354 | .. "Expected:\n" 355 | .. "(values list) ()", 356 | 1, true) 357 | end) 358 | end) 359 | -------------------------------------------------------------------------------- /spec/state_spec.lua: -------------------------------------------------------------------------------- 1 | describe("Tests states of the assert engine", function() 2 | 3 | it("checks levels created/reverted", function() 4 | local start = assert:snapshot() 5 | assert.is_nil(start.next) 6 | 7 | local snapshot1 = assert:snapshot() 8 | assert.is.table(start.next) 9 | assert.are.equal(start.next, snapshot1) 10 | assert.are.equal(start, snapshot1.previous) 11 | assert.is_nil(snapshot1.next) 12 | 13 | local snapshot2 = assert:snapshot() 14 | assert.is.table(snapshot1.next) 15 | assert.are.equal(snapshot2, snapshot1.next) 16 | assert.are.equal(snapshot2.previous, snapshot1) 17 | assert.is_nil(snapshot2.next) 18 | 19 | snapshot2:revert() 20 | assert.is.table(start.next) 21 | assert.are.equal(start.next, snapshot1) 22 | assert.are.equal(start, snapshot1.previous) 23 | assert.is_nil(snapshot1.next) 24 | 25 | snapshot1:revert() 26 | assert.is_nil(start.next) 27 | end) 28 | 29 | it("checks to see if a formatter is reversed", function() 30 | 31 | -- add a state level by creating a snapshot 32 | local snapshot1 = assert:snapshot() 33 | -- register extra formatters 34 | local fmt1 = function(value) 35 | if type(value) == "string" then return "ok" end 36 | end 37 | assert:add_formatter(fmt1) 38 | local fmt2 = function(value) 39 | if type(value) == "number" then return "1" end 40 | end 41 | assert:add_formatter(fmt2) 42 | -- check formatters 43 | assert.are.equal(#snapshot1.formatters, 2) 44 | assert.are.equal(snapshot1.formatters[2], fmt1) 45 | assert.are.equal(snapshot1.formatters[1], fmt2) 46 | assert.are.equal("ok", assert:format({"some value"})[1]) 47 | assert.are.equal("1", assert:format({123})[1]) 48 | 49 | -- add another state level by creating a snapshot 50 | local snapshot2 = assert:snapshot() 51 | -- register extra formatter 52 | local fmt3 = function(value) 53 | if type(value) == "number" then return "2" end 54 | end 55 | assert:add_formatter(fmt3) 56 | assert.are.equal(#snapshot2.formatters, 1) 57 | assert.are.equal(snapshot2.formatters[1], fmt3) 58 | -- check formatter newest level 59 | assert.are.equal("2", assert:format({123})[1]) 60 | -- check formatter previous level 61 | assert.are.equal("ok", assert:format({"some value"})[1]) 62 | -- check formatter initial level 63 | assert.are.equal("(boolean) true", assert:format({true})[1]) 64 | 65 | -- revert 1 state up 66 | snapshot2:revert() 67 | assert.is_nil(snapshot1.next) 68 | assert.are.equal(2, #snapshot1.formatters) 69 | -- check formatter reverted level 70 | assert.are.equal("1", assert:format({123})[1]) 71 | -- check formatter unchanged level 72 | assert.are.equal("ok", assert:format({"some value"})[1]) 73 | -- check formatter unchanged level 74 | assert.are.equal("(boolean) true", assert:format({true})[1]) 75 | 76 | -- revert 1 more up, to initial level 77 | snapshot1:revert() 78 | assert.are.equal("(number) 123", assert:format({123})[1]) 79 | assert.are.equal("(string) 'some value'", assert:format({"some value"})[1]) 80 | assert.are.equal("(boolean) true", assert:format({true})[1]) 81 | end) 82 | 83 | it("checks to see if a parameter is reversed", function() 84 | 85 | -- add a state level by creating a snapshot 86 | local snapshot1 = assert:snapshot() 87 | assert.is_nil(assert:get_parameter("Test_1")) 88 | assert.is_nil(assert:get_parameter("Test_2")) 89 | assert:set_parameter("Test_1", 1) 90 | assert:set_parameter("Test_2", 2) 91 | assert.are.equal(1, assert:get_parameter("Test_1")) 92 | assert.are.equal(2, assert:get_parameter("Test_2")) 93 | 94 | -- add another state level by creating a snapshot 95 | local snapshot2 = assert:snapshot() 96 | assert.are.equal(1, assert:get_parameter("Test_1")) 97 | assert.are.equal(2, assert:get_parameter("Test_2")) 98 | assert:set_parameter("Test_1", "one") 99 | assert:set_parameter("Test_2", nil) -- test setting to nil 100 | assert.are.equal("one", assert:get_parameter("Test_1")) 101 | assert.is_nil(assert:get_parameter("Test_2")) 102 | 103 | -- revert 1 state up 104 | snapshot2:revert() 105 | assert.are.equal(1, assert:get_parameter("Test_1")) 106 | assert.are.equal(2, assert:get_parameter("Test_2")) 107 | 108 | -- revert 1 more up, to initial level 109 | snapshot1:revert() 110 | assert.is_nil(assert:get_parameter("Test_1")) 111 | assert.is_nil(assert:get_parameter("Test_2")) 112 | end) 113 | 114 | it("checks to see if a spy/stub is reversed", function() 115 | 116 | local c1, c2 = 0, 0 117 | local test = { 118 | f1 = function() c1 = c1 + 1 end, 119 | f2 = function() c2 = c2 + 1 end, 120 | } 121 | -- add a state level by creating a snapshot 122 | local snapshot1 = assert:snapshot() 123 | -- create spy/stub 124 | local s1 = spy.on(test, "f1") 125 | local s2 = stub(test, "f2") 126 | -- call them both 127 | test.f1() 128 | test.f2() 129 | assert.spy(test.f1).was.called(1) 130 | assert.spy(test.f2).was.called(1) 131 | assert.is_equal(1, c1) 132 | assert.is_equal(0, c2) -- 0, because it's a stub 133 | 134 | -- revert to initial level 135 | snapshot1:revert() 136 | test.f1() 137 | test.f2() 138 | -- check count is still 1 for both 139 | assert.spy(s1).was.called(1) 140 | assert.spy(s2).was.called(1) 141 | assert.is_equal(2, c1) 142 | assert.is_equal(1, c2) 143 | end) 144 | 145 | end) 146 | -------------------------------------------------------------------------------- /spec/stub_spec.lua: -------------------------------------------------------------------------------- 1 | local match = require 'luassert.match' 2 | 3 | describe("Tests dealing with stubs", function() 4 | local test = {} 5 | 6 | before_each(function() 7 | test = {key = function() 8 | return "derp" 9 | end} 10 | end) 11 | 12 | it("checks to see if stub keeps track of arguments", function() 13 | stub(test, "key") 14 | test.key("derp") 15 | assert.stub(test.key).was.called_with("derp") 16 | assert.error_matches( 17 | function() assert.stub(test.key).was.called_with("herp") end, 18 | "Function was never called with matching arguments.\n" 19 | .. "Called with (last call if any):\n" 20 | .. "(values list) ((string) 'derp')\n" 21 | .. "Expected:\n" 22 | .. "(values list) ((string) 'herp')", 23 | 1, true) 24 | end) 25 | 26 | it("checks to see if stub keeps track of number of calls", function() 27 | stub(test, "key") 28 | test.key() 29 | test.key("test") 30 | assert.stub(test.key).was.called(2) 31 | end) 32 | 33 | it("checks called() and called_with() assertions", function() 34 | local s = stub.new(test, "key") 35 | 36 | s(1, 2, 3) 37 | s("a", "b", "c") 38 | assert.stub(s).was.called() 39 | assert.stub(s).was.called(2) -- twice! 40 | assert.stub(s).was_not.called(3) 41 | assert.stub(s).was_not.called_with({1, 2, 3}) -- mind the accolades 42 | assert.stub(s).was.called_with(1, 2, 3) 43 | assert.error_matches( 44 | function() assert.stub(s).was.called_with(5, 6) end, 45 | "Function was never called with matching arguments.\n" 46 | .. "Called with (last call if any):\n" 47 | .. "(values list) ((string) 'a', (string) 'b', (string) 'c')\n" 48 | .. "Expected:\n" 49 | .. "(values list) ((number) 5, (number) 6)", 50 | 1, true) 51 | end) 52 | 53 | it("checks stub to fail when spying on non-callable elements", function() 54 | local s 55 | local testfunc = function() 56 | local t = { key = s} 57 | stub.new(t, "key") 58 | end 59 | -- try some types to fail 60 | s = "some string"; assert.has_error(testfunc) 61 | s = 10; assert.has_error(testfunc) 62 | s = true; assert.has_error(testfunc) 63 | -- try some types to succeed 64 | s = function() end; assert.has_no_error(testfunc) 65 | s = setmetatable( {}, { __call = function() end } ); assert.has_no_error(testfunc) 66 | end) 67 | 68 | it("checks reverting a stub call", function() 69 | local calls = 0 70 | local old = function() calls = calls + 1 end 71 | test.key = old 72 | local s = stub.new(test, "key") 73 | assert.is_table(s) 74 | s() 75 | s() 76 | assert.stub(s).was.called(2) 77 | assert.are.equal(calls, 0) -- its a stub, so no calls 78 | local old_s = s 79 | s = s:revert() 80 | s() 81 | assert.stub(old_s).was.called(2) -- still two, stub was removed 82 | assert.are.equal(s, old) 83 | assert.are.equal(calls, 1) -- restored, so now 1 call 84 | end) 85 | 86 | it("checks reverting a stub call on a nil value", function() 87 | test = {} 88 | local s = stub.new(test, "key") 89 | assert.is_table(s) 90 | s() 91 | s() 92 | assert.stub(s).was.called(2) 93 | s = s:revert() 94 | assert.is_nil(s) 95 | end) 96 | 97 | it("checks creating and reverting a 'blank' stub", function() 98 | local s = stub.new() -- use no parameters to create a blank 99 | assert.is_table(s) 100 | s() 101 | s() 102 | assert.stub(s).was.called(2) 103 | s = s:revert() 104 | assert.is_nil(s) 105 | end) 106 | 107 | it("checks clearing a stub only clears call history", function() 108 | local s = stub.new(test, "key") 109 | s.returns("value") 110 | s.on_call_with("foo").returns("bar") 111 | s() 112 | s("foo") 113 | s:clear() 114 | assert.stub(s).was_not.called() 115 | assert.stub(s).was_not.returned_with("value") 116 | assert.stub(s).was_not.returned_with("bar") 117 | s("foo") 118 | assert.stub(s).was.returned_with("bar") 119 | end) 120 | 121 | it("returns nil by default", function() 122 | stub(test, "key") 123 | 124 | assert.is_nil(test.key()) 125 | end) 126 | 127 | it("returns a given return value", function() 128 | stub(test, "key", "foo") 129 | 130 | assert.is.equal("foo", test.key()) 131 | end) 132 | 133 | it("returns multiple given values", function() 134 | stub(test, "key", "foo", nil, "bar") 135 | 136 | local arg1, arg2, arg3 = test.key() 137 | 138 | assert.is.equal("foo", arg1) 139 | assert.is.equal(nil, arg2) 140 | assert.is.equal("bar", arg3) 141 | end) 142 | 143 | it("calls specified stub function", function() 144 | stub(test, "key", function(a, b, c) 145 | return c, b, a 146 | end) 147 | 148 | local arg1, arg2, arg3 = test.key("bar", nil, "foo") 149 | 150 | assert.is.equal("foo", arg1) 151 | assert.is.equal(nil, arg2) 152 | assert.is.equal("bar", arg3) 153 | end) 154 | 155 | it("calls specified stub callable object", function() 156 | local callable = setmetatable({}, { __call = function(self, a, b, c) 157 | return c, b, a 158 | end}) 159 | stub(test, "key", callable) 160 | 161 | local arg1, arg2, arg3 = test.key("bar", nil, "foo") 162 | 163 | assert.is.equal("foo", arg1) 164 | assert.is.equal(nil, arg2) 165 | assert.is.equal("bar", arg3) 166 | end) 167 | 168 | it("returning multiple given values overrides stub function", function() 169 | local function foo() end 170 | stub(test, "key", foo, nil, "bar") 171 | 172 | local arg1, arg2, arg3 = test.key() 173 | 174 | assert.is.equal(foo, arg1) 175 | assert.is.equal(nil, arg2) 176 | assert.is.equal("bar", arg3) 177 | end) 178 | 179 | it("returns default stub arguments", function() 180 | stub(test, "key").returns(nil, "foo", "bar") 181 | 182 | local arg1, arg2, arg3 = test.key("bar", nil, "foo") 183 | 184 | assert.is.equal(nil, arg1) 185 | assert.is.equal("foo", arg2) 186 | assert.is.equal("bar", arg3) 187 | end) 188 | 189 | it("invokes default stub function", function() 190 | stub(test, "key").invokes(function(a, b, c) 191 | return c, b, a 192 | end) 193 | 194 | local arg1, arg2, arg3 = test.key("bar", nil, "foo") 195 | 196 | assert.is.equal("foo", arg1) 197 | assert.is.equal(nil, arg2) 198 | assert.is.equal("bar", arg3) 199 | end) 200 | 201 | it("returns stub arguments by default", function() 202 | stub(test, "key").by_default.returns("foo", "bar") 203 | 204 | local arg1, arg2 = test.key() 205 | 206 | assert.is.equal("foo", arg1) 207 | assert.is.equal("bar", arg2) 208 | end) 209 | 210 | it("invokes stub function by default", function() 211 | stub(test, "key").by_default.invokes(function(a, b) 212 | return b, a 213 | end) 214 | 215 | local arg1, arg2 = test.key("bar", "foo") 216 | 217 | assert.is.equal("foo", arg1) 218 | assert.is.equal("bar", arg2) 219 | end) 220 | 221 | it("on_call_with returns specified arguments", function() 222 | stub(test, "key").returns("foo bar") 223 | test.key.on_call_with("bar").returns("foo", nil, "bar") 224 | test.key.on_call_with(match._, "foo").returns("foofoo") 225 | 226 | local arg1, arg2, arg3 = test.key("bar") 227 | local foofoo1 = test.key(1, "foo") 228 | local foofoo2 = test.key(2, "foo") 229 | local foofoo3 = test.key(nil, "foo") 230 | local foobar = test.key() 231 | 232 | assert.is.equal("foo", arg1) 233 | assert.is.equal(nil, arg2) 234 | assert.is.equal("bar", arg3) 235 | assert.is.equal("foo bar", foobar) 236 | assert.is.equal("foofoo", foofoo1) 237 | assert.is.equal("foofoo", foofoo2) 238 | assert.is.equal("foofoo", foofoo3) 239 | end) 240 | 241 | it("on_call_with invokes stub function", function() 242 | stub(test, "key").returns("foo foo") 243 | test.key.on_call_with("foo").invokes(function(a, b, c) 244 | return "bar", nil, "bar" 245 | end) 246 | 247 | local arg1, arg2, arg3 = test.key("foo") 248 | local foo = test.key() 249 | 250 | assert.is.equal("bar", arg1) 251 | assert.is.equal(nil, arg2) 252 | assert.is.equal("bar", arg3) 253 | assert.is.equal("foo foo", foo) 254 | end) 255 | 256 | it("on_call_with matches arguments for returns", function() 257 | local t = { foo = { bar = { "test" } } } 258 | stub(test, "key").returns("foo foo") 259 | test.key.on_call_with(t).returns("bar") 260 | t.foo.bar = "value" 261 | 262 | local bar = test.key({ foo = { bar = { "test" } } }) 263 | local foofoo = test.key(t) 264 | 265 | assert.is.equal("bar", bar) 266 | assert.is.equal("foo foo", foofoo) 267 | end) 268 | 269 | it("on_call_with matches arguments for invokes", function() 270 | local t = { foo = { bar = { "test" } } } 271 | stub(test, "key").returns("foo foo") 272 | test.key.on_call_with(t).invokes(function() return "bar bar" end) 273 | t.foo.bar = "value" 274 | 275 | local bar = test.key({ foo = { bar = { "test" } } }) 276 | local foofoo = test.key(t) 277 | 278 | assert.is.equal("bar bar", bar) 279 | assert.is.equal("foo foo", foofoo) 280 | end) 281 | 282 | it("on_call_with matches arguments using refs", function() 283 | local t1 = { foo = { bar = { "test" } } } 284 | local t2 = { foo = { bar = { "test" } } } 285 | stub(test, "key").returns("foo foo") 286 | test.key.on_call_with(match.is_ref(t1)).returns("bar") 287 | t1.foo.bar = "value" 288 | t2.foo.bar = "value" 289 | 290 | local bar = test.key(t1) 291 | local foo = test.key(t2) 292 | local foofoo = test.key({ foo = { bar = { "test" } } }) 293 | 294 | assert.is.equal("bar", bar) 295 | assert.is.equal("foo foo", foo) 296 | assert.is.equal("foo foo", foofoo) 297 | end) 298 | 299 | end) 300 | -------------------------------------------------------------------------------- /src/array.lua: -------------------------------------------------------------------------------- 1 | local assert = require('luassert.assert') 2 | local say = require('say') 3 | 4 | -- Example usage: 5 | -- local arr = { "one", "two", "three" } 6 | -- 7 | -- assert.array(arr).has.no.holes() -- checks the array to not contain holes --> passes 8 | -- assert.array(arr).has.no.holes(4) -- sets explicit length to 4 --> fails 9 | -- 10 | -- local first_hole = assert.array(arr).has.holes(4) -- check array of size 4 to contain holes --> passes 11 | -- assert.equal(4, first_hole) -- passes, as the index of the first hole is returned 12 | 13 | 14 | -- Unique key to store the object we operate on in the state object 15 | -- key must be unique, to make sure we do not have name collissions in the shared state object 16 | local ARRAY_STATE_KEY = "__array_state" 17 | 18 | -- The modifier, to store the object in our state 19 | local function array(state, args, level) 20 | assert(args.n > 0, "No array provided to the array-modifier") 21 | assert(rawget(state, ARRAY_STATE_KEY) == nil, "Array already set") 22 | rawset(state, ARRAY_STATE_KEY, args[1]) 23 | return state 24 | end 25 | 26 | -- The actual assertion that operates on our object, stored via the modifier 27 | local function holes(state, args, level) 28 | local length = args[1] 29 | local arr = rawget(state, ARRAY_STATE_KEY) -- retrieve previously set object 30 | -- only check against nil, metatable types are allowed 31 | assert(arr ~= nil, "No array set, please use the array modifier to set the array to validate") 32 | if length == nil then 33 | length = 0 34 | for i in pairs(arr) do 35 | if type(i) == "number" and 36 | i > length and 37 | math.floor(i) == i then 38 | length = i 39 | end 40 | end 41 | end 42 | assert(type(length) == "number", "expected array length to be of type 'number', got: "..tostring(length)) 43 | -- let's do the actual assertion 44 | local missing 45 | for i = 1, length do 46 | if arr[i] == nil then 47 | missing = i 48 | break 49 | end 50 | end 51 | -- format arguments for output strings; 52 | args[1] = missing 53 | args.n = missing and 1 or 0 54 | return missing ~= nil, { missing } -- assert result + first missing index as return value 55 | end 56 | 57 | -- Register the proper assertion messages 58 | say:set("assertion.array_holes.positive", [[ 59 | Expected array to have holes, but none were found. 60 | ]]) 61 | say:set("assertion.array_holes.negative", [[ 62 | Expected array to not have holes, but found a hole at index: %s 63 | ]]) 64 | 65 | -- Register the assertion, and the modifier 66 | assert:register("assertion", "holes", holes, 67 | "assertion.array_holes.positive", 68 | "assertion.array_holes.negative") 69 | 70 | assert:register("modifier", "array", array) 71 | -------------------------------------------------------------------------------- /src/assert.lua: -------------------------------------------------------------------------------- 1 | local s = require 'say' 2 | local astate = require 'luassert.state' 3 | local util = require 'luassert.util' 4 | local unpack = util.unpack 5 | local obj -- the returned module table 6 | local level_mt = {} 7 | 8 | -- list of namespaces 9 | local namespace = require 'luassert.namespaces' 10 | 11 | local function geterror(assertion_message, failure_message, args) 12 | if util.hastostring(failure_message) then 13 | failure_message = tostring(failure_message) 14 | elseif failure_message ~= nil then 15 | failure_message = astate.format_argument(failure_message) 16 | end 17 | local message = s(assertion_message, obj:format(args)) 18 | if message and failure_message then 19 | message = failure_message .. "\n" .. message 20 | end 21 | return message or failure_message 22 | end 23 | 24 | local __state_meta = { 25 | 26 | __call = function(self, ...) 27 | local keys = util.extract_keys("assertion", self.tokens) 28 | 29 | local assertion 30 | 31 | for _, key in ipairs(keys) do 32 | assertion = namespace.assertion[key] or assertion 33 | end 34 | 35 | if assertion then 36 | for _, key in ipairs(keys) do 37 | if namespace.modifier[key] then 38 | namespace.modifier[key].callback(self) 39 | end 40 | end 41 | 42 | local arguments = util.make_arglist(...) 43 | local val, retargs = assertion.callback(self, arguments, util.errorlevel()) 44 | 45 | if (not val) == self.mod then 46 | local message = assertion.positive_message 47 | if not self.mod then 48 | message = assertion.negative_message 49 | end 50 | local err = geterror(message, rawget(self,"failure_message"), arguments) 51 | error(err or "assertion failed!", util.errorlevel()) 52 | end 53 | 54 | if retargs then 55 | return unpack(retargs) 56 | end 57 | return ... 58 | else 59 | local arguments = util.make_arglist(...) 60 | self.tokens = {} 61 | 62 | for _, key in ipairs(keys) do 63 | if namespace.modifier[key] then 64 | namespace.modifier[key].callback(self, arguments, util.errorlevel()) 65 | end 66 | end 67 | end 68 | 69 | return self 70 | end, 71 | 72 | __index = function(self, key) 73 | for token in key:lower():gmatch('[^_]+') do 74 | table.insert(self.tokens, token) 75 | end 76 | 77 | return self 78 | end 79 | } 80 | 81 | obj = { 82 | state = function() return setmetatable({mod=true, tokens={}}, __state_meta) end, 83 | 84 | -- registers a function in namespace 85 | register = function(self, nspace, name, callback, positive_message, negative_message) 86 | local lowername = name:lower() 87 | if not namespace[nspace] then 88 | namespace[nspace] = {} 89 | end 90 | namespace[nspace][lowername] = { 91 | callback = callback, 92 | name = lowername, 93 | positive_message=positive_message, 94 | negative_message=negative_message 95 | } 96 | end, 97 | 98 | -- unregisters a function in a namespace 99 | unregister = function(self, nspace, name) 100 | local lowername = name:lower() 101 | if not namespace[nspace] then 102 | namespace[nspace] = {} 103 | end 104 | namespace[nspace][lowername] = nil 105 | end, 106 | 107 | -- registers a formatter 108 | -- a formatter takes a single argument, and converts it to a string, or returns nil if it cannot format the argument 109 | add_formatter = function(self, callback) 110 | astate.add_formatter(callback) 111 | end, 112 | 113 | -- unregisters a formatter 114 | remove_formatter = function(self, fmtr) 115 | astate.remove_formatter(fmtr) 116 | end, 117 | 118 | format = function(self, args) 119 | -- args.n specifies the number of arguments in case of 'trailing nil' arguments which get lost 120 | local nofmt = args.nofmt or {} -- arguments in this list should not be formatted 121 | local fmtargs = args.fmtargs or {} -- additional arguments to be passed to formatter 122 | for i = 1, (args.n or #args) do -- cannot use pairs because table might have nils 123 | if not nofmt[i] then 124 | local val = args[i] 125 | local valfmt = astate.format_argument(val, nil, fmtargs[i]) 126 | if valfmt == nil then valfmt = tostring(val) end -- no formatter found 127 | args[i] = valfmt 128 | end 129 | end 130 | return args 131 | end, 132 | 133 | set_parameter = function(self, name, value) 134 | astate.set_parameter(name, value) 135 | end, 136 | 137 | get_parameter = function(self, name) 138 | return astate.get_parameter(name) 139 | end, 140 | 141 | add_spy = function(self, spy) 142 | astate.add_spy(spy) 143 | end, 144 | 145 | snapshot = function(self) 146 | return astate.snapshot() 147 | end, 148 | 149 | level = function(self, level) 150 | return setmetatable({ 151 | level = level 152 | }, level_mt) 153 | end, 154 | 155 | -- returns the level if a level-value, otherwise nil 156 | get_level = function(self, level) 157 | if getmetatable(level) ~= level_mt then 158 | return nil -- not a valid error-level 159 | end 160 | return level.level 161 | end, 162 | } 163 | 164 | local __meta = { 165 | 166 | __call = function(self, bool, message, level, ...) 167 | if not bool then 168 | local err_level = (self:get_level(level) or 1) + 1 169 | error(message or "assertion failed!", err_level) 170 | end 171 | return bool , message , level , ... 172 | end, 173 | 174 | __index = function(self, key) 175 | return rawget(self, key) or self.state()[key] 176 | end, 177 | 178 | } 179 | 180 | return setmetatable(obj, __meta) 181 | -------------------------------------------------------------------------------- /src/assertions.lua: -------------------------------------------------------------------------------- 1 | -- module will not return anything, only register assertions with the main assert engine 2 | 3 | -- assertions take 2 parameters; 4 | -- 1) state 5 | -- 2) arguments list. The list has a member 'n' with the argument count to check for trailing nils 6 | -- 3) level The level of the error position relative to the called function 7 | -- returns; boolean; whether assertion passed 8 | 9 | local assert = require('luassert.assert') 10 | local astate = require ('luassert.state') 11 | local util = require ('luassert.util') 12 | local s = require('say') 13 | 14 | local function format(val) 15 | return astate.format_argument(val) or tostring(val) 16 | end 17 | 18 | local function set_failure_message(state, message) 19 | if message ~= nil then 20 | state.failure_message = message 21 | end 22 | end 23 | 24 | local function unique(state, arguments, level) 25 | local list = arguments[1] 26 | local deep 27 | local argcnt = arguments.n 28 | if type(arguments[2]) == "boolean" or (arguments[2] == nil and argcnt > 2) then 29 | deep = arguments[2] 30 | set_failure_message(state, arguments[3]) 31 | else 32 | if type(arguments[3]) == "boolean" then 33 | deep = arguments[3] 34 | end 35 | set_failure_message(state, arguments[2]) 36 | end 37 | for k,v in pairs(list) do 38 | for k2, v2 in pairs(list) do 39 | if k ~= k2 then 40 | if deep and util.deepcompare(v, v2, true) then 41 | return false 42 | else 43 | if v == v2 then 44 | return false 45 | end 46 | end 47 | end 48 | end 49 | end 50 | return true 51 | end 52 | 53 | local function near(state, arguments, level) 54 | local level = (level or 1) + 1 55 | local argcnt = arguments.n 56 | assert(argcnt > 2, s("assertion.internal.argtolittle", { "near", 3, tostring(argcnt) }), level) 57 | local expected = tonumber(arguments[1]) 58 | local actual = tonumber(arguments[2]) 59 | local tolerance = tonumber(arguments[3]) 60 | local numbertype = "number or object convertible to a number" 61 | assert(expected, s("assertion.internal.badargtype", { 1, "near", numbertype, format(arguments[1]) }), level) 62 | assert(actual, s("assertion.internal.badargtype", { 2, "near", numbertype, format(arguments[2]) }), level) 63 | assert(tolerance, s("assertion.internal.badargtype", { 3, "near", numbertype, format(arguments[3]) }), level) 64 | -- switch arguments for proper output message 65 | util.tinsert(arguments, 1, util.tremove(arguments, 2)) 66 | arguments[3] = tolerance 67 | arguments.nofmt = arguments.nofmt or {} 68 | arguments.nofmt[3] = true 69 | set_failure_message(state, arguments[4]) 70 | return (actual >= expected - tolerance and actual <= expected + tolerance) 71 | end 72 | 73 | local function matches(state, arguments, level) 74 | local level = (level or 1) + 1 75 | local argcnt = arguments.n 76 | assert(argcnt > 1, s("assertion.internal.argtolittle", { "matches", 2, tostring(argcnt) }), level) 77 | local pattern = arguments[1] 78 | local actual = nil 79 | if util.hastostring(arguments[2]) or type(arguments[2]) == "number" then 80 | actual = tostring(arguments[2]) 81 | end 82 | local err_message 83 | local init_arg_num = 3 84 | for i=3,argcnt,1 do 85 | if arguments[i] and type(arguments[i]) ~= "boolean" and not tonumber(arguments[i]) then 86 | if i == 3 then init_arg_num = init_arg_num + 1 end 87 | err_message = util.tremove(arguments, i) 88 | break 89 | end 90 | end 91 | local init = arguments[3] 92 | local plain = arguments[4] 93 | local stringtype = "string or object convertible to a string" 94 | assert(type(pattern) == "string", s("assertion.internal.badargtype", { 1, "matches", "string", type(arguments[1]) }), level) 95 | assert(actual, s("assertion.internal.badargtype", { 2, "matches", stringtype, format(arguments[2]) }), level) 96 | assert(init == nil or tonumber(init), s("assertion.internal.badargtype", { init_arg_num, "matches", "number", type(arguments[3]) }), level) 97 | -- switch arguments for proper output message 98 | util.tinsert(arguments, 1, util.tremove(arguments, 2)) 99 | set_failure_message(state, err_message) 100 | local retargs 101 | local ok 102 | if plain then 103 | ok = (actual:find(pattern, init, plain) ~= nil) 104 | retargs = ok and { pattern } or {} 105 | else 106 | retargs = { actual:match(pattern, init) } 107 | ok = (retargs[1] ~= nil) 108 | end 109 | return ok, retargs 110 | end 111 | 112 | local function equals(state, arguments, level) 113 | local level = (level or 1) + 1 114 | local argcnt = arguments.n 115 | assert(argcnt > 1, s("assertion.internal.argtolittle", { "equals", 2, tostring(argcnt) }), level) 116 | local result = arguments[1] == arguments[2] 117 | -- switch arguments for proper output message 118 | util.tinsert(arguments, 1, util.tremove(arguments, 2)) 119 | set_failure_message(state, arguments[3]) 120 | return result 121 | end 122 | 123 | local function same(state, arguments, level) 124 | local level = (level or 1) + 1 125 | local argcnt = arguments.n 126 | assert(argcnt > 1, s("assertion.internal.argtolittle", { "same", 2, tostring(argcnt) }), level) 127 | if type(arguments[1]) == 'table' and type(arguments[2]) == 'table' then 128 | local result, crumbs = util.deepcompare(arguments[1], arguments[2], true) 129 | -- switch arguments for proper output message 130 | util.tinsert(arguments, 1, util.tremove(arguments, 2)) 131 | arguments.fmtargs = arguments.fmtargs or {} 132 | arguments.fmtargs[1] = { crumbs = crumbs } 133 | arguments.fmtargs[2] = { crumbs = crumbs } 134 | set_failure_message(state, arguments[3]) 135 | return result 136 | end 137 | local result = arguments[1] == arguments[2] 138 | -- switch arguments for proper output message 139 | util.tinsert(arguments, 1, util.tremove(arguments, 2)) 140 | set_failure_message(state, arguments[3]) 141 | return result 142 | end 143 | 144 | local function truthy(state, arguments, level) 145 | local argcnt = arguments.n 146 | assert(argcnt > 0, s("assertion.internal.argtolittle", { "truthy", 1, tostring(argcnt) }), level) 147 | set_failure_message(state, arguments[2]) 148 | return arguments[1] ~= false and arguments[1] ~= nil 149 | end 150 | 151 | local function falsy(state, arguments, level) 152 | local argcnt = arguments.n 153 | assert(argcnt > 0, s("assertion.internal.argtolittle", { "falsy", 1, tostring(argcnt) }), level) 154 | return not truthy(state, arguments, level) 155 | end 156 | 157 | local function has_error(state, arguments, level) 158 | local level = (level or 1) + 1 159 | local retargs = util.shallowcopy(arguments) 160 | local func = arguments[1] 161 | local err_expected = arguments[2] 162 | local failure_message = arguments[3] 163 | assert(util.callable(func), s("assertion.internal.badargtype", { 1, "error", "function or callable object", type(func) }), level) 164 | local ok, err_actual = pcall(func) 165 | if type(err_actual) == 'string' then 166 | -- remove 'path/to/file:line: ' from string 167 | err_actual = err_actual:gsub('^.-:%d+: ', '', 1) 168 | end 169 | retargs[1] = err_actual 170 | arguments.nofmt = {} 171 | arguments.n = 2 172 | arguments[1] = (ok and '(no error)' or err_actual) 173 | arguments[2] = (err_expected == nil and '(error)' or err_expected) 174 | arguments.nofmt[1] = ok 175 | arguments.nofmt[2] = (err_expected == nil) 176 | set_failure_message(state, failure_message) 177 | 178 | if ok or err_expected == nil then 179 | return not ok, retargs 180 | end 181 | if type(err_expected) == 'string' then 182 | -- err_actual must be (convertible to) a string 183 | if util.hastostring(err_actual) then 184 | err_actual = tostring(err_actual) 185 | retargs[1] = err_actual 186 | end 187 | if type(err_actual) == 'string' then 188 | return err_expected == err_actual, retargs 189 | end 190 | elseif type(err_expected) == 'number' then 191 | if type(err_actual) == 'string' then 192 | return tostring(err_expected) == tostring(tonumber(err_actual)), retargs 193 | end 194 | end 195 | return same(state, {err_expected, err_actual, ["n"] = 2}), retargs 196 | end 197 | 198 | local function error_matches(state, arguments, level) 199 | local level = (level or 1) + 1 200 | local retargs = util.shallowcopy(arguments) 201 | local argcnt = arguments.n 202 | local func = arguments[1] 203 | local pattern = arguments[2] 204 | assert(argcnt > 1, s("assertion.internal.argtolittle", { "error_matches", 2, tostring(argcnt) }), level) 205 | assert(util.callable(func), s("assertion.internal.badargtype", { 1, "error_matches", "function or callable object", type(func) }), level) 206 | assert(pattern == nil or type(pattern) == "string", s("assertion.internal.badargtype", { 2, "error", "string", type(pattern) }), level) 207 | 208 | local failure_message 209 | local init_arg_num = 3 210 | for i=3,argcnt,1 do 211 | if arguments[i] and type(arguments[i]) ~= "boolean" and not tonumber(arguments[i]) then 212 | if i == 3 then init_arg_num = init_arg_num + 1 end 213 | failure_message = util.tremove(arguments, i) 214 | break 215 | end 216 | end 217 | local init = arguments[3] 218 | local plain = arguments[4] 219 | assert(init == nil or tonumber(init), s("assertion.internal.badargtype", { init_arg_num, "matches", "number", type(arguments[3]) }), level) 220 | 221 | local ok, err_actual = pcall(func) 222 | if type(err_actual) == 'string' then 223 | -- remove 'path/to/file:line: ' from string 224 | err_actual = err_actual:gsub('^.-:%d+: ', '', 1) 225 | end 226 | retargs[1] = err_actual 227 | arguments.nofmt = {} 228 | arguments.n = 2 229 | arguments[1] = (ok and '(no error)' or err_actual) 230 | arguments[2] = pattern 231 | arguments.nofmt[1] = ok 232 | arguments.nofmt[2] = false 233 | set_failure_message(state, failure_message) 234 | 235 | if ok then return not ok, retargs end 236 | if err_actual == nil and pattern == nil then 237 | return true, {} 238 | end 239 | 240 | -- err_actual must be (convertible to) a string 241 | if util.hastostring(err_actual) or 242 | type(err_actual) == "number" or 243 | type(err_actual) == "boolean" then 244 | err_actual = tostring(err_actual) 245 | retargs[1] = err_actual 246 | end 247 | if type(err_actual) == 'string' then 248 | local ok 249 | local retargs_ok 250 | if plain then 251 | retargs_ok = { pattern } 252 | ok = (err_actual:find(pattern, init, plain) ~= nil) 253 | else 254 | retargs_ok = { err_actual:match(pattern, init) } 255 | ok = (retargs_ok[1] ~= nil) 256 | end 257 | if ok then retargs = retargs_ok end 258 | return ok, retargs 259 | end 260 | 261 | return false, retargs 262 | end 263 | 264 | local function is_true(state, arguments, level) 265 | util.tinsert(arguments, 2, true) 266 | set_failure_message(state, arguments[3]) 267 | return arguments[1] == arguments[2] 268 | end 269 | 270 | local function is_false(state, arguments, level) 271 | util.tinsert(arguments, 2, false) 272 | set_failure_message(state, arguments[3]) 273 | return arguments[1] == arguments[2] 274 | end 275 | 276 | local function is_type(state, arguments, level, etype) 277 | util.tinsert(arguments, 2, "type " .. etype) 278 | arguments.nofmt = arguments.nofmt or {} 279 | arguments.nofmt[2] = true 280 | set_failure_message(state, arguments[3]) 281 | return arguments.n > 1 and type(arguments[1]) == etype 282 | end 283 | 284 | local function returned_arguments(state, arguments, level) 285 | arguments[1] = tostring(arguments[1]) 286 | arguments[2] = tostring(arguments.n - 1) 287 | arguments.nofmt = arguments.nofmt or {} 288 | arguments.nofmt[1] = true 289 | arguments.nofmt[2] = true 290 | if arguments.n < 2 then arguments.n = 2 end 291 | return arguments[1] == arguments[2] 292 | end 293 | 294 | local function set_message(state, arguments, level) 295 | state.failure_message = arguments[1] 296 | end 297 | 298 | local function is_boolean(state, arguments, level) return is_type(state, arguments, level, "boolean") end 299 | local function is_number(state, arguments, level) return is_type(state, arguments, level, "number") end 300 | local function is_string(state, arguments, level) return is_type(state, arguments, level, "string") end 301 | local function is_table(state, arguments, level) return is_type(state, arguments, level, "table") end 302 | local function is_nil(state, arguments, level) return is_type(state, arguments, level, "nil") end 303 | local function is_userdata(state, arguments, level) return is_type(state, arguments, level, "userdata") end 304 | local function is_function(state, arguments, level) return is_type(state, arguments, level, "function") end 305 | local function is_thread(state, arguments, level) return is_type(state, arguments, level, "thread") end 306 | 307 | assert:register("modifier", "message", set_message) 308 | assert:register("assertion", "true", is_true, "assertion.same.positive", "assertion.same.negative") 309 | assert:register("assertion", "false", is_false, "assertion.same.positive", "assertion.same.negative") 310 | assert:register("assertion", "boolean", is_boolean, "assertion.same.positive", "assertion.same.negative") 311 | assert:register("assertion", "number", is_number, "assertion.same.positive", "assertion.same.negative") 312 | assert:register("assertion", "string", is_string, "assertion.same.positive", "assertion.same.negative") 313 | assert:register("assertion", "table", is_table, "assertion.same.positive", "assertion.same.negative") 314 | assert:register("assertion", "nil", is_nil, "assertion.same.positive", "assertion.same.negative") 315 | assert:register("assertion", "userdata", is_userdata, "assertion.same.positive", "assertion.same.negative") 316 | assert:register("assertion", "function", is_function, "assertion.same.positive", "assertion.same.negative") 317 | assert:register("assertion", "thread", is_thread, "assertion.same.positive", "assertion.same.negative") 318 | assert:register("assertion", "returned_arguments", returned_arguments, "assertion.returned_arguments.positive", "assertion.returned_arguments.negative") 319 | 320 | assert:register("assertion", "same", same, "assertion.same.positive", "assertion.same.negative") 321 | assert:register("assertion", "matches", matches, "assertion.matches.positive", "assertion.matches.negative") 322 | assert:register("assertion", "match", matches, "assertion.matches.positive", "assertion.matches.negative") 323 | assert:register("assertion", "near", near, "assertion.near.positive", "assertion.near.negative") 324 | assert:register("assertion", "equals", equals, "assertion.equals.positive", "assertion.equals.negative") 325 | assert:register("assertion", "equal", equals, "assertion.equals.positive", "assertion.equals.negative") 326 | assert:register("assertion", "unique", unique, "assertion.unique.positive", "assertion.unique.negative") 327 | assert:register("assertion", "error", has_error, "assertion.error.positive", "assertion.error.negative") 328 | assert:register("assertion", "errors", has_error, "assertion.error.positive", "assertion.error.negative") 329 | assert:register("assertion", "error_matches", error_matches, "assertion.error.positive", "assertion.error.negative") 330 | assert:register("assertion", "error_match", error_matches, "assertion.error.positive", "assertion.error.negative") 331 | assert:register("assertion", "matches_error", error_matches, "assertion.error.positive", "assertion.error.negative") 332 | assert:register("assertion", "match_error", error_matches, "assertion.error.positive", "assertion.error.negative") 333 | assert:register("assertion", "truthy", truthy, "assertion.truthy.positive", "assertion.truthy.negative") 334 | assert:register("assertion", "falsy", falsy, "assertion.falsy.positive", "assertion.falsy.negative") 335 | -------------------------------------------------------------------------------- /src/compatibility.lua: -------------------------------------------------------------------------------- 1 | -- no longer needed, only for backward compatibility 2 | local unpack = require ("luassert.util").unpack 3 | 4 | return { 5 | unpack = function(...) 6 | print(debug.traceback("WARN: calling deprecated function 'luassert.compatibility.unpack' use 'luassert.util.unpack' instead")) 7 | return unpack(...) 8 | end 9 | } 10 | -------------------------------------------------------------------------------- /src/formatters/binarystring.lua: -------------------------------------------------------------------------------- 1 | local format = function (str) 2 | if type(str) ~= "string" then return nil end 3 | local result = "Binary string length; " .. tostring(#str) .. " bytes\n" 4 | local i = 1 5 | local hex = "" 6 | local chr = "" 7 | while i <= #str do 8 | local byte = str:byte(i) 9 | hex = string.format("%s%2x ", hex, byte) 10 | if byte < 32 then byte = string.byte(".") end 11 | chr = chr .. string.char(byte) 12 | if math.floor(i/16) == i/16 or i == #str then 13 | -- reached end of line 14 | hex = hex .. string.rep(" ", 16 * 3 - #hex) 15 | chr = chr .. string.rep(" ", 16 - #chr) 16 | 17 | result = result .. hex:sub(1, 8 * 3) .. " " .. hex:sub(8*3+1, -1) .. " " .. chr:sub(1,8) .. " " .. chr:sub(9,-1) .. "\n" 18 | 19 | hex = "" 20 | chr = "" 21 | end 22 | i = i + 1 23 | end 24 | return result 25 | end 26 | 27 | return format 28 | 29 | -------------------------------------------------------------------------------- /src/formatters/init.lua: -------------------------------------------------------------------------------- 1 | -- module will not return anything, only register formatters with the main assert engine 2 | local assert = require('luassert.assert') 3 | local match = require('luassert.match') 4 | local util = require('luassert.util') 5 | 6 | local isatty, colors do 7 | local ok, term = pcall(require, 'term') 8 | isatty = io.type(io.stdout) == 'file' and ok and term.isatty(io.stdout) 9 | if not isatty then 10 | local isWindows = package.config:sub(1,1) == '\\' 11 | if isWindows and os.getenv("ANSICON") then 12 | isatty = true 13 | end 14 | end 15 | 16 | colors = setmetatable({ 17 | none = function(c) return c end 18 | },{ __index = function(self, key) 19 | return function(c) 20 | for token in key:gmatch("[^%.]+") do 21 | c = term.colors[token](c) 22 | end 23 | return c 24 | end 25 | end 26 | }) 27 | end 28 | 29 | local function fmt_string(arg) 30 | if type(arg) == "string" then 31 | return string.format("(string) '%s'", arg) 32 | end 33 | end 34 | 35 | -- A version of tostring which formats numbers more precisely. 36 | local function tostr(arg) 37 | if type(arg) ~= "number" then 38 | return tostring(arg) 39 | end 40 | 41 | if arg ~= arg then 42 | return "NaN" 43 | elseif arg == 1/0 then 44 | return "Inf" 45 | elseif arg == -1/0 then 46 | return "-Inf" 47 | end 48 | 49 | local str = string.format("%.20g", arg) 50 | 51 | if math.type and math.type(arg) == "float" and not str:find("[%.,]") then 52 | -- Number is a float but looks like an integer. 53 | -- Insert ".0" after first run of digits. 54 | str = str:gsub("%d+", "%0.0", 1) 55 | end 56 | 57 | return str 58 | end 59 | 60 | local function fmt_number(arg) 61 | if type(arg) == "number" then 62 | return string.format("(number) %s", tostr(arg)) 63 | end 64 | end 65 | 66 | local function fmt_boolean(arg) 67 | if type(arg) == "boolean" then 68 | return string.format("(boolean) %s", tostring(arg)) 69 | end 70 | end 71 | 72 | local function fmt_nil(arg) 73 | if type(arg) == "nil" then 74 | return "(nil)" 75 | end 76 | end 77 | 78 | local type_priorities = { 79 | number = 1, 80 | boolean = 2, 81 | string = 3, 82 | table = 4, 83 | ["function"] = 5, 84 | userdata = 6, 85 | thread = 7 86 | } 87 | 88 | local function is_in_array_part(key, length) 89 | return type(key) == "number" and 1 <= key and key <= length and math.floor(key) == key 90 | end 91 | 92 | local function get_sorted_keys(t) 93 | local keys = {} 94 | local nkeys = 0 95 | 96 | for key in pairs(t) do 97 | nkeys = nkeys + 1 98 | keys[nkeys] = key 99 | end 100 | 101 | local length = #t 102 | 103 | local function key_comparator(key1, key2) 104 | local type1, type2 = type(key1), type(key2) 105 | local priority1 = is_in_array_part(key1, length) and 0 or type_priorities[type1] or 8 106 | local priority2 = is_in_array_part(key2, length) and 0 or type_priorities[type2] or 8 107 | 108 | if priority1 == priority2 then 109 | if type1 == "string" or type1 == "number" then 110 | return key1 < key2 111 | elseif type1 == "boolean" then 112 | return key1 -- put true before false 113 | end 114 | else 115 | return priority1 < priority2 116 | end 117 | end 118 | 119 | table.sort(keys, key_comparator) 120 | return keys, nkeys 121 | end 122 | 123 | local function fmt_table(arg, fmtargs) 124 | if type(arg) ~= "table" then 125 | return 126 | end 127 | 128 | local tmax = assert:get_parameter("TableFormatLevel") 129 | local showrec = assert:get_parameter("TableFormatShowRecursion") 130 | local errchar = assert:get_parameter("TableErrorHighlightCharacter") or "" 131 | local errcolor = assert:get_parameter("TableErrorHighlightColor") 132 | local crumbs = fmtargs and fmtargs.crumbs or {} 133 | local cache = {} 134 | local type_desc 135 | 136 | if getmetatable(arg) == nil then 137 | type_desc = "(" .. tostring(arg) .. ") " 138 | elseif not pcall(setmetatable, arg, getmetatable(arg)) then 139 | -- cannot set same metatable, so it is protected, skip id 140 | type_desc = "(table) " 141 | else 142 | -- unprotected metatable, temporary remove the mt 143 | local mt = getmetatable(arg) 144 | setmetatable(arg, nil) 145 | type_desc = "(" .. tostring(arg) .. ") " 146 | setmetatable(arg, mt) 147 | end 148 | 149 | local function ft(t, l, with_crumbs) 150 | if showrec and cache[t] and cache[t] > 0 then 151 | return "{ ... recursive }" 152 | end 153 | 154 | if next(t) == nil then 155 | return "{ }" 156 | end 157 | 158 | if l > tmax and tmax >= 0 then 159 | return "{ ... more }" 160 | end 161 | 162 | local result = "{" 163 | local keys, nkeys = get_sorted_keys(t) 164 | 165 | cache[t] = (cache[t] or 0) + 1 166 | local crumb = crumbs[#crumbs - l + 1] 167 | 168 | for i = 1, nkeys do 169 | local k = keys[i] 170 | local v = t[k] 171 | local use_crumbs = with_crumbs and k == crumb 172 | 173 | if type(v) == "table" then 174 | v = ft(v, l + 1, use_crumbs) 175 | elseif type(v) == "string" then 176 | v = "'"..v.."'" 177 | end 178 | 179 | local ch = use_crumbs and errchar or "" 180 | local indent = string.rep(" ",l * 2 - ch:len()) 181 | local mark = (ch:len() == 0 and "" or colors[errcolor](ch)) 182 | result = result .. string.format("\n%s%s[%s] = %s", indent, mark, tostr(k), tostr(v)) 183 | end 184 | 185 | cache[t] = cache[t] - 1 186 | 187 | return result .. " }" 188 | end 189 | 190 | return type_desc .. ft(arg, 1, true) 191 | end 192 | 193 | local function fmt_function(arg) 194 | if type(arg) == "function" then 195 | local debug_info = debug.getinfo(arg) 196 | return string.format("%s @ line %s in %s", tostring(arg), tostring(debug_info.linedefined), tostring(debug_info.source)) 197 | end 198 | end 199 | 200 | local function fmt_userdata(arg) 201 | if type(arg) == "userdata" then 202 | return string.format("(userdata) '%s'", tostring(arg)) 203 | end 204 | end 205 | 206 | local function fmt_thread(arg) 207 | if type(arg) == "thread" then 208 | return string.format("(thread) '%s'", tostring(arg)) 209 | end 210 | end 211 | 212 | local function fmt_matcher(arg) 213 | if not match.is_matcher(arg) then 214 | return 215 | end 216 | local not_inverted = { 217 | [true] = "is.", 218 | [false] = "no.", 219 | } 220 | local args = {} 221 | for idx = 1, arg.arguments.n do 222 | table.insert(args, assert:format({ arg.arguments[idx], n = 1, })[1]) 223 | end 224 | return string.format("(matcher) " .. (arg.name == "_" and "_ *anything*" or "%s%s(%s)"), 225 | not_inverted[arg.mod], 226 | tostring(arg.name), 227 | table.concat(args, ", ")) 228 | end 229 | 230 | local function fmt_arglist(arglist) 231 | if not util.is_arglist(arglist) then 232 | return 233 | end 234 | local formatted_vals = {} 235 | for idx = 1, arglist.n do 236 | table.insert(formatted_vals, assert:format({ arglist[idx], n = 1, })[1]) 237 | end 238 | return "(values list) (" .. table.concat(formatted_vals, ", ") .. ")" 239 | end 240 | 241 | assert:add_formatter(fmt_string) 242 | assert:add_formatter(fmt_number) 243 | assert:add_formatter(fmt_boolean) 244 | assert:add_formatter(fmt_nil) 245 | assert:add_formatter(fmt_table) 246 | assert:add_formatter(fmt_function) 247 | assert:add_formatter(fmt_userdata) 248 | assert:add_formatter(fmt_thread) 249 | assert:add_formatter(fmt_matcher) 250 | assert:add_formatter(fmt_arglist) 251 | -- Set default table display depth for table formatter 252 | assert:set_parameter("TableFormatLevel", 3) 253 | assert:set_parameter("TableFormatShowRecursion", false) 254 | assert:set_parameter("TableErrorHighlightCharacter", "*") 255 | assert:set_parameter("TableErrorHighlightColor", isatty and "red" or "none") 256 | -------------------------------------------------------------------------------- /src/init.lua: -------------------------------------------------------------------------------- 1 | local assert = require('luassert.assert') 2 | 3 | assert._COPYRIGHT = "Copyright (c) 2018 Olivine Labs, LLC." 4 | assert._DESCRIPTION = "Extends Lua's built-in assertions to provide additional tests and the ability to create your own." 5 | assert._VERSION = "Luassert 1.8.0" 6 | 7 | -- load basic asserts 8 | require('luassert.assertions') 9 | require('luassert.modifiers') 10 | require('luassert.array') 11 | require('luassert.matchers') 12 | require('luassert.formatters') 13 | 14 | -- load default language 15 | require('luassert.languages.en') 16 | 17 | return assert 18 | -------------------------------------------------------------------------------- /src/languages/ar.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace("ar") 4 | 5 | s:set("assertion.same.positive", "تُوُقِّعَ تَماثُلُ الكائِنات.\nتَمَّ إدخال:\n %s.\nبَينَما كانَ مِن المُتَوقَّع:\n %s.") 6 | s:set("assertion.same.negative", "تُوُقِّعَ إختِلافُ الكائِنات.\nتَمَّ إدخال:\n %s.\nبَينَما كانَ مِن غَيرِ المُتَوقَّع:\n %s.") 7 | 8 | s:set("assertion.equals.positive", "تُوُقِّعَ أن تَتَساوىْ الكائِنات.\nتمَّ إِدخال:\n %s.\nبَينَما كانَ من المُتَوقَّع:\n %s.") 9 | s:set("assertion.equals.negative", "تُوُقِّعَ ألّا تَتَساوىْ الكائِنات.\nتمَّ إِدخال:\n %s.\nبَينَما كانَ مِن غير المُتًوقَّع:\n %s.") 10 | 11 | s:set("assertion.unique.positive", "تُوُقِّعَ أَنْ يَكونَ الكائِنٌ فَريد: \n%s") 12 | s:set("assertion.unique.negative", "تُوُقِّعَ أنْ يَكونَ الكائِنٌ غَيرَ فَريد: \n%s") 13 | 14 | s:set("assertion.error.positive", "تُوُقِّعَ إصدارُ خطأْ.") 15 | s:set("assertion.error.negative", "تُوُقِّعَ عدم إصدارِ خطأ.") 16 | 17 | s:set("assertion.truthy.positive", "تُوُقِّعَت قيمةٌ صَحيحة، بينما كانت: \n%s") 18 | s:set("assertion.truthy.negative", "تُوُقِّعَت قيمةٌ غيرُ صَحيحة، بينما كانت: \n%s") 19 | 20 | s:set("assertion.falsy.positive", "تُوُقِّعَت قيمةٌ خاطِئة، بَينَما كانت: \n%s") 21 | s:set("assertion.falsy.negative", "تُوُقِّعَت قيمةٌ غيرُ خاطِئة، بَينَما كانت: \n%s") 22 | -------------------------------------------------------------------------------- /src/languages/de.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace('de') 4 | 5 | s:set("assertion.same.positive", "Erwarte gleiche Objekte.\nGegeben:\n%s\nErwartet:\n%s") 6 | s:set("assertion.same.negative", "Erwarte ungleiche Objekte.\nGegeben:\n%s\nNicht erwartet:\n%s") 7 | 8 | s:set("assertion.equals.positive", "Erwarte dieselben Objekte.\nGegeben:\n%s\nErwartet:\n%s") 9 | s:set("assertion.equals.negative", "Erwarte nicht dieselben Objekte.\nGegeben:\n%s\nNicht erwartet:\n%s") 10 | 11 | s:set("assertion.near.positive", "Erwarte annähernd gleiche Werte.\nGegeben:\n%s\nErwartet:\n%s +/- %s") 12 | s:set("assertion.near.negative", "Erwarte keine annähernd gleichen Werte.\nGegeben:\n%s\nNicht erwartet:\n%s +/- %s") 13 | 14 | s:set("assertion.matches.positive", "Erwarte identische Zeichenketten.\nGegeben:\n%s\nErwartet:\n%s") 15 | s:set("assertion.matches.negative", "Erwarte unterschiedliche Zeichenketten.\nGegeben:\n%s\nNicht erwartet:\n%s") 16 | 17 | s:set("assertion.unique.positive", "Erwarte einzigartiges Objekt:\n%s") 18 | s:set("assertion.unique.negative", "Erwarte nicht einzigartiges Objekt:\n%s") 19 | 20 | s:set("assertion.error.positive", "Es wird ein Fehler erwartet.") 21 | s:set("assertion.error.negative", "Es wird kein Fehler erwartet, aber folgender Fehler trat auf:\n%s") 22 | 23 | s:set("assertion.truthy.positive", "Erwarte, dass der Wert 'wahr' (truthy) ist.\nGegeben:\n%s") 24 | s:set("assertion.truthy.negative", "Erwarte, dass der Wert 'unwahr' ist (falsy).\nGegeben:\n%s") 25 | 26 | s:set("assertion.falsy.positive", "Erwarte, dass der Wert 'unwahr' ist (falsy).\nGegeben:\n%s") 27 | s:set("assertion.falsy.negative", "Erwarte, dass der Wert 'wahr' (truthy) ist.\nGegeben:\n%s") 28 | 29 | s:set("assertion.called.positive", "Erwarte, dass die Funktion %s-mal aufgerufen wird, anstatt %s mal.") 30 | s:set("assertion.called.negative", "Erwarte, dass die Funktion nicht genau %s-mal aufgerufen wird.") 31 | 32 | s:set("assertion.called_at_least.positive", "Erwarte, dass die Funktion mindestens %s-mal aufgerufen wird, anstatt %s-mal") 33 | s:set("assertion.called_at_most.positive", "Erwarte, dass die Funktion höchstens %s-mal aufgerufen wird, anstatt %s-mal") 34 | s:set("assertion.called_more_than.positive", "Erwarte, dass die Funktion mehr als %s-mal aufgerufen wird, anstatt %s-mal") 35 | s:set("assertion.called_less_than.positive", "Erwarte, dass die Funktion weniger als %s-mal aufgerufen wird, anstatt %s-mal") 36 | 37 | s:set("assertion.called_with.positive", "Erwarte, dass die Funktion mit den gegebenen Parametern aufgerufen wird.") 38 | s:set("assertion.called_with.negative", "Erwarte, dass die Funktion nicht mit den gegebenen Parametern aufgerufen wird.") 39 | 40 | s:set("assertion.returned_with.positive", "Die Funktion wurde nicht mit den Argumenten zurückgegeben.") 41 | s:set("assertion.returned_with.negative", "Die Funktion wurde mit den Argumenten zurückgegeben.") 42 | 43 | s:set("assertion.returned_arguments.positive", "Erwarte den Aufruf mit %s Argument(en), aber der Aufruf erfolgte mit %s") 44 | s:set("assertion.returned_arguments.negative", "Erwarte nicht den Aufruf mit %s Argument(en), der Aufruf erfolgte dennoch mit %s") 45 | 46 | -- errors 47 | s:set("assertion.internal.argtolittle", "Die Funktion '%s' erwartet mindestens %s Parameter, gegeben: %s") 48 | s:set("assertion.internal.badargtype", "bad argument #%s: Die Funktion '%s' erwartet einen Parameter vom Typ %s, gegeben: %s") 49 | -------------------------------------------------------------------------------- /src/languages/en.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace('en') 4 | 5 | s:set("assertion.same.positive", "Expected objects to be the same.\nPassed in:\n%s\nExpected:\n%s") 6 | s:set("assertion.same.negative", "Expected objects to not be the same.\nPassed in:\n%s\nDid not expect:\n%s") 7 | 8 | s:set("assertion.equals.positive", "Expected objects to be equal.\nPassed in:\n%s\nExpected:\n%s") 9 | s:set("assertion.equals.negative", "Expected objects to not be equal.\nPassed in:\n%s\nDid not expect:\n%s") 10 | 11 | s:set("assertion.near.positive", "Expected values to be near.\nPassed in:\n%s\nExpected:\n%s +/- %s") 12 | s:set("assertion.near.negative", "Expected values to not be near.\nPassed in:\n%s\nDid not expect:\n%s +/- %s") 13 | 14 | s:set("assertion.matches.positive", "Expected strings to match.\nPassed in:\n%s\nExpected:\n%s") 15 | s:set("assertion.matches.negative", "Expected strings not to match.\nPassed in:\n%s\nDid not expect:\n%s") 16 | 17 | s:set("assertion.unique.positive", "Expected object to be unique:\n%s") 18 | s:set("assertion.unique.negative", "Expected object to not be unique:\n%s") 19 | 20 | s:set("assertion.error.positive", "Expected a different error.\nCaught:\n%s\nExpected:\n%s") 21 | s:set("assertion.error.negative", "Expected no error, but caught:\n%s") 22 | 23 | s:set("assertion.truthy.positive", "Expected to be truthy, but value was:\n%s") 24 | s:set("assertion.truthy.negative", "Expected to not be truthy, but value was:\n%s") 25 | 26 | s:set("assertion.falsy.positive", "Expected to be falsy, but value was:\n%s") 27 | s:set("assertion.falsy.negative", "Expected to not be falsy, but value was:\n%s") 28 | 29 | s:set("assertion.called.positive", "Expected to be called %s time(s), but was called %s time(s)") 30 | s:set("assertion.called.negative", "Expected not to be called exactly %s time(s), but it was.") 31 | 32 | s:set("assertion.called_at_least.positive", "Expected to be called at least %s time(s), but was called %s time(s)") 33 | s:set("assertion.called_at_most.positive", "Expected to be called at most %s time(s), but was called %s time(s)") 34 | s:set("assertion.called_more_than.positive", "Expected to be called more than %s time(s), but was called %s time(s)") 35 | s:set("assertion.called_less_than.positive", "Expected to be called less than %s time(s), but was called %s time(s)") 36 | 37 | s:set("assertion.called_with.positive", "Function was never called with matching arguments.\nCalled with (last call if any):\n%s\nExpected:\n%s") 38 | s:set("assertion.called_with.negative", "Function was called with matching arguments at least once.\nCalled with (last matching call):\n%s\nDid not expect:\n%s") 39 | 40 | s:set("assertion.returned_with.positive", "Function never returned matching arguments.\nReturned (last call if any):\n%s\nExpected:\n%s") 41 | s:set("assertion.returned_with.negative", "Function returned matching arguments at least once.\nReturned (last matching call):\n%s\nDid not expect:\n%s") 42 | 43 | s:set("assertion.returned_arguments.positive", "Expected %s results(s) to be returned, but got %s") 44 | s:set("assertion.returned_arguments.negative", "Expected not %s results(s) to be returned, but got %s") 45 | 46 | -- errors 47 | s:set("assertion.internal.argtolittle", "the '%s' function requires a minimum of %s arguments, got: %s") 48 | s:set("assertion.internal.badargtype", "bad argument #%s to '%s' (%s expected, got %s)") 49 | -------------------------------------------------------------------------------- /src/languages/fr.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace('fr') 4 | 5 | s:set("assertion.called.positive", "Prévu pour être appelé %s fois(s), mais a été appelé %s fois(s).") 6 | s:set("assertion.called.negative", "Prévu de ne pas être appelé exactement %s fois(s), mais ceci a été le cas.") 7 | 8 | s:set("assertion.called_at_least.positive", "Prévu pour être appelé au moins %s fois(s), mais a été appelé %s fois(s).") 9 | s:set("assertion.called_at_most.positive", "Prévu pour être appelé au plus %s fois(s), mais a été appelé %s fois(s).") 10 | 11 | s:set("assertion.called_more_than.positive", "Devrait être appelé plus de %s fois(s), mais a été appelé %s fois(s).") 12 | s:set("assertion.called_less_than.positive", "Devrait être appelé moins de %s fois(s), mais a été appelé %s fois(s).") 13 | 14 | s:set("assertion.called_with.positive", "La fonction n'a pas été appelée avec les arguments.") 15 | s:set("assertion.called_with.negative", "La fonction a été appelée avec les arguments.") 16 | 17 | s:set("assertion.equals.positive", "Les objets attendus doivent être égaux. \n Argument passé en: \n %s \n Attendu: \n %s.") 18 | s:set("assertion.equals.negative", "Les objets attendus ne doivent pas être égaux. \n Argument passé en: \n %s \n Non attendu: \n %s.") 19 | 20 | s:set("assertion.error.positive", "Une erreur différente est attendue. \n Prise: \n %s \n Attendue: \n %s.") 21 | s:set("assertion.error.negative", "Aucune erreur attendue, mais prise: \n %s.") 22 | 23 | s:set("assertion.falsy.positive", "Assertion supposée etre fausse mais de valeur: \n %s") 24 | s:set("assertion.falsy.negative", "Assertion supposée etre vraie mais de valeur: \n %s") 25 | 26 | -- errors 27 | s:set("assertion.internal.argtolittle", "La fonction '%s' requiert un minimum de %s arguments, obtenu: %s.") 28 | s:set("assertion.internal.badargtype", "Mauvais argument #%s pour '%s' (%s attendu, obtenu %s).") 29 | -- errors 30 | 31 | s:set("assertion.matches.positive", "Chaînes attendues pour correspondre. \n Argument passé en: \n %s \n Attendu: \n %s.") 32 | s:set("assertion.matches.negative", "Les chaînes attendues ne doivent pas correspondre. \n Argument passé en: \n %s \n Non attendu: \n %s.") 33 | 34 | s:set("assertion.near.positive", "Les valeurs attendues sont proches. \n Argument passé en: \n %s \n Attendu: \n %s +/- %s.") 35 | s:set("assertion.near.negative", "Les valeurs attendues ne doivent pas être proches. \n Argument passé en: \n %s \n Non attendu: \n %s +/- %s.") 36 | 37 | s:set("assertion.returned_arguments.positive", "Attendu pour être appelé avec le(s) argument(s) %s, mais a été appelé avec %s.") 38 | s:set("assertion.returned_arguments.negative", "Attendu pour ne pas être appelé avec le(s) argument(s) %s, mais a été appelé avec %s.") 39 | 40 | s:set("assertion.returned_with.positive", "La fonction n'a pas été retournée avec les arguments.") 41 | s:set("assertion.returned_with.negative", "La fonction a été retournée avec les arguments.") 42 | 43 | s:set("assertion.same.positive", "Les objets attendus sont les mêmes. \n Argument passé en: \n %s \n Attendu: \n %s.") 44 | s:set("assertion.same.negative", "Les objets attendus ne doivent pas être les mêmes. \n Argument passé en: \n %s \n Non attendu: \n %s.") 45 | 46 | s:set("assertion.truthy.positive", "Assertion supposee etre vraie mais de valeur: \n %s") 47 | s:set("assertion.truthy.negative", "Assertion supposee etre fausse mais de valeur: \n %s") 48 | 49 | s:set("assertion.unique.positive", "Objet attendu pour être unique: \n %s.") 50 | s:set("assertion.unique.negative", "Objet attendu pour ne pas être unique: \n %s.") 51 | -------------------------------------------------------------------------------- /src/languages/id.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace('id') 4 | 5 | s:set("assertion.same.positive", "Objek yang diharapkan akan sama.\nLulus:\n%s\nDiharapkan:\n%s") 6 | s:set("assertion.same.negative", "Objek yang diharapkan tidak sama.\nLulus:\n%s\nTidak diharapkan:\n%s") 7 | 8 | s:set("assertion.equals.positive", "Objek yang diharapkan setara.\nLulus:\n%s\nDiharapkan:\n%s") 9 | s:set("assertion.equals.negative", "Objek yang diharapkan tidak setara.\nLulus:\n%s\nTidak diharapkan:\n%s") 10 | 11 | s:set("assertion.near.positive", "Nilai yang diharapkan mendekati.\nLulus:\n%s\nDiharapkan:\n%s +/- %s") 12 | s:set("assertion.near.negative", "Nilai yang diharapkan tidak mendekati.\nLulus:\n%s\nTidak diharapkan:\n%s +/- %s") 13 | 14 | s:set("assertion.matches.positive", "String yang diharapkan cocok.\nLulus:\n%s\nDiharapkan:\n%s") 15 | s:set("assertion.matches.negative", "String yang diharapkan tidak cocok.\nLulus:\n%s\nTidak diharapkan:\n%s") 16 | 17 | s:set("assertion.unique.positive", "Objek yang diharapkan unik:\n%s") 18 | s:set("assertion.unique.negative", "Objek yang diharapkan tidak unik:\n%s") 19 | 20 | s:set("assertion.error.positive", "Mengharapkan kesalahan yang berbeda.\nTertangkap:\n%s\nDiharapkan:\n%s") 21 | s:set("assertion.error.negative", "Mengharapkan tidak ada kesalahan, tetapi menangkap:\n%s") 22 | 23 | s:set("assertion.truthy.positive", "Diharapkan benar, tetapi nilainya adalah:\n%s") 24 | s:set("assertion.truthy.negative", "Diharapkan tidak benar, tetapi nilainya adalah:\n%s") 25 | 26 | s:set("assertion.falsy.positive", "Diharapkan salah, tetapi nilainya adalah:\n%s") 27 | s:set("assertion.falsy.negative", "Diharapkan tidak salah, tetapi nilainya adalah:\n%s") 28 | 29 | s:set("assertion.called.positive", "Diharapkan dipanggil %s kali, tetapi dipanggil %s kali") 30 | s:set("assertion.called.negative", "Diharapkan tidak dipanggil tepat %s kali, tapi seperti itu.") 31 | 32 | s:set("assertion.called_at_least.positive", "Diharapkan dipanggil setidaknya %s kali, tetapi dipanggil %s kali") 33 | s:set("assertion.called_at_most.positive", "Diharapkan dipanggil paling banyak %s kali, tetapi dipanggil %s kali") 34 | s:set("assertion.called_more_than.positive", "Diharapkan dipanggil lebih dari %s kali, tetapi dipanggil %s kali") 35 | s:set("assertion.called_less_than.positive", "Diharapkan dipanggil kurang dari %s kali, tetapi dipanggil %s kali") 36 | 37 | s:set("assertion.called_with.positive", "Fungsi tidak pernah dipanggil dengan argumen yang cocok.\nDipanggil dengan (panggilan terakhir jika ada):\n%s\nDiharapkan:\n%s") 38 | s:set("assertion.called_with.negative", "Fungsi dipanggil dengan argumen yang cocok setidaknya sekali.\nDipanggil dengan (panggilan terakhir yang cocok):\n%s\nTidak diharapkan:\n%s") 39 | 40 | s:set("assertion.returned_with.positive", "Fungsi tidak pernah mengembalikan argumen yang cocok.\nMengembalikan (panggilan terakhir jika ada):\n%s\nDiharapkan:\n%s") 41 | s:set("assertion.returned_with.negative", "Fungsi mengembalikan argumen yang cocok setidaknya sekali.\nMengembalikan (panggilan terakhir yang cocok):\n%s\nTidak diharapkan:\n%s") 42 | 43 | s:set("assertion.returned_arguments.positive", "Diharapkan dipanggil dengan argumen %s, tetapi dipanggil dengan %s") 44 | s:set("assertion.returned_arguments.negative", "Diharapkan tidak dipanggil dengan argumen %s, tetapi dipanggil dengan %s") 45 | 46 | -- errors 47 | s:set("assertion.internal.argtolittle", "fungsi '%s' membutuhkan sedikitnya %s argumen, didapat: %s") 48 | s:set("assertion.internal.badargtype", "Kesalahan pada argumen ke #%s untuk '%s' (%s diharapkan, mendapatkan %s)") 49 | -------------------------------------------------------------------------------- /src/languages/is.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace('is') 4 | 5 | s:set("assertion.same.positive", "Átti von á að vera eins.\nSett inn:\n%s\nBjóst við:\n%s") 6 | s:set("assertion.same.negative", "Átti von á að ekki vera eins.\nSett inn:\n%s\nBjóst ekki við:\n%s") 7 | 8 | s:set("assertion.equals.positive", "Átti von á að vera jöfn.\nSett inn:\n%s\nBjóst við:\n%s") 9 | s:set("assertion.equals.negative", "Átti von á að ekki vera jöfn.\nSett inn:\n%s\nBjóst ekki við:\n%s") 10 | 11 | s:set("assertion.near.positive", "Átti von á að vera svipuð.\nSett inn:\n%s\nBjóst við:\n%s +/- %s") 12 | s:set("assertion.near.negative", "Átti von á að ekki vera svipuð.\nSett inn:\n%s\nBjóst ekki við:\n%s +/- %s") 13 | 14 | s:set("assertion.matches.positive", "Átti von á að strengir væru eins.\nSett inn:\n%s\nBjóst við:\n%s") 15 | s:set("assertion.matches.negative", "Átti von á að strengir væru ekki eins.\nSett inn:\n%s\nBjóst ekki við:\n%s") 16 | 17 | s:set("assertion.unique.positive", "Átti von á að vera einstök:\n%s") 18 | s:set("assertion.unique.negative", "Átti von á að ekki vera einstök:\n%s") 19 | 20 | s:set("assertion.error.positive", "Átti von á annarri villu.\nFékk:\n%s\nBjóst við:\n%s") 21 | s:set("assertion.error.negative", "Átti ekki von á neinni villu en fékk:\n%s") 22 | 23 | s:set("assertion.truthy.positive", "Átti von á sönnu gildi en gildi var:\n%s") 24 | s:set("assertion.truthy.negative", "Átti ekki von á sönnu gildi en gildi var:\n%s") 25 | 26 | s:set("assertion.falsy.positive", "Átti von á ósönnu gildi en gildi var:\n%s") 27 | s:set("assertion.falsy.negative", "Átti ekki von á ósönnu gildi en gildi var:\n%s") 28 | 29 | s:set("assertion.called.positive", "Átti von á að vera kallað %s sinnum en var kallað %s sinnum") 30 | s:set("assertion.called.negative", "Átti von á að ekki vera kallað %s sinnum en var") 31 | 32 | s:set("assertion.called_at_least.positive", "Átti von á að vera kallað að minnsta kosti %s sinnum an var kallað %s sinnum") 33 | s:set("assertion.called_at_most.positive", "Átti von á að vera kallað að mesta lagi %s sinnum an var kallað %s sinnum") 34 | s:set("assertion.called_more_than.positive", "Átti von á að vera kallað oftar en %s sinnum an var kallað %s sinnum") 35 | s:set("assertion.called_less_than.positive", "Átti von á að vera kallað færra en %s sinnum an var kallað %s sinnum") 36 | 37 | s:set("assertion.called_with.positive", "Undirforrit var aldrei kallað með passandi færibreytum.\nKallað með (síðasta):\n%s\nBjóst við:\n%s") 38 | s:set("assertion.called_with.negative", "Undirforrit var kallað með passandi færibreytum.\nKallað með (síðasta):\n%s\nBjóst ekki við:\n%s") 39 | 40 | s:set("assertion.returned_with.positive", "Undirforrit skilaði aldrei samsvarandi breytu.\nSkilað (síðasta):\n%s\nBjóst við:\n%s") 41 | s:set("assertion.returned_with.negative", "Undirforrit skilaði samsvarandi breytu.\nSkilað (síðasta):\n%s\nBjóst ekki við:\n%s") 42 | 43 | s:set("assertion.returned_arguments.positive", "Átti von á að vera kallað með færibreytum %s en var kallað með %s") 44 | s:set("assertion.returned_arguments.negative", "Átti von á að ekki vera kallað með færibreytum %s en var kallað með %s") 45 | 46 | -- errors 47 | s:set("assertion.internal.argtolittle", "undirforritið „%s“ krefst lágmarks %s færibreyti en fékk: %s") 48 | s:set("assertion.internal.badargtype", "slæmt færibreyti #%s til „%s“ (átti von á %s en fékk %s)") 49 | -------------------------------------------------------------------------------- /src/languages/ja.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace('ja') 4 | 5 | s:set("assertion.same.positive", "オブジェクトの内容が同一であることが期待されています。\n実際の値:\n%s\n期待されている値:\n%s") 6 | s:set("assertion.same.negative", "オブジェクトの内容が同一でないことが期待されています。\n実際の値:\n%s\n期待されていない値:\n%s") 7 | 8 | s:set("assertion.equals.positive", "オブジェクトが同一であることが期待されています。\n実際の値:\n%s\n期待されている値:\n%s") 9 | s:set("assertion.equals.negative", "オブジェクトが同一でないことが期待されています。\n実際の値:\n%s\n期待されていない値:\n%s") 10 | 11 | s:set("assertion.near.positive", "値が近いことが期待されています。\n実際の値:\n%s\n期待されている値:\n%s +/- %s") 12 | s:set("assertion.near.negative", "値が近くないことが期待されています。\n実際の値:\n%s\n期待されていない値:\n%s +/- %s") 13 | 14 | s:set("assertion.matches.positive", "文字列が一致することが期待されています。\n実際の値:\n%s\n期待されている値:\n%s") 15 | s:set("assertion.matches.negative", "文字列が一致しないことが期待されています。\n実際の値:\n%s\n期待されていない値:\n%s") 16 | 17 | s:set("assertion.unique.positive", "オブジェクトがユニークであることが期待されています。:\n%s") 18 | s:set("assertion.unique.negative", "オブジェクトがユニークでないことが期待されています。:\n%s") 19 | 20 | s:set("assertion.error.positive", "エラーが発生することが期待されています。") 21 | s:set("assertion.error.negative", "エラーが発生しないことが期待されています。") 22 | 23 | s:set("assertion.truthy.positive", "真であることが期待されていますが、値は:\n%s") 24 | s:set("assertion.truthy.negative", "真でないことが期待されていますが、値は:\n%s") 25 | 26 | s:set("assertion.falsy.positive", "偽であることが期待されていますが、値は:\n%s") 27 | s:set("assertion.falsy.negative", "偽でないことが期待されていますが、値は:\n%s") 28 | 29 | s:set("assertion.called.positive", "%s回呼ばれることを期待されていますが、実際には%s回呼ばれています。") 30 | s:set("assertion.called.negative", "%s回呼ばれることを期待されていますが、実際には%s回呼ばれています。") 31 | 32 | s:set("assertion.called_at_least.positive", "少なくとの%s回呼ばれることを期待されていますが、実際には%s回呼ばれています。") 33 | s:set("assertion.called_at_most.positive", "多くとの%s回呼ばれることを期待されていますが、実際には%s回呼ばれています。") 34 | s:set("assertion.called_more_than.positive", "%s回より多く呼ばれることを期待されていますが、実際には%s回呼ばれています。") 35 | s:set("assertion.called_less_than.positive", "%s回より少なく呼ばれることを期待されていますが、実際には%s回呼ばれています。") 36 | 37 | s:set("assertion.called_with.positive", "関数が期待されている引数で呼ばれていません") 38 | s:set("assertion.called_with.negative", "関数が期待されている引数で呼ばれています") 39 | 40 | s:set("assertion.returned_with.positive", "関数が期待されている返り値で呼ばれていません。\n(あれば)実際の返り値:\n%s\n期待されている返り値:\n%s") 41 | s:set("assertion.returned_with.negative", "関数が期待されてない返り値で一回以上呼ばれています。\n(最後の)返り値:\n%s\n期待されていない返り値:\n%s") 42 | 43 | s:set("assertion.returned_arguments.positive", "期待されている返り値の数は%sですが、実際の返り値の数は%sです。") 44 | s:set("assertion.returned_arguments.negative", "期待されていない返り値の数は%sですが、実際の返り値の数は%sです。") 45 | 46 | -- errors 47 | s:set("assertion.internal.argtolittle", "関数には最低%s個の引数が必要ですが、実際の引数の数は: %s") 48 | s:set("assertion.internal.badargtype", "bad argument #%s: 関数には%s個の引数が必要ですが、実際に引数の数は: %s") 49 | -------------------------------------------------------------------------------- /src/languages/ko.lua: -------------------------------------------------------------------------------- 1 | local s = require("say") 2 | 3 | s:set_namespace("ko") 4 | 5 | s:set("assertion.same.positive", "객체 내용이 같을 것으로 기대함.\n실제값:\n%s\n기대값:\n%s") 6 | s:set( 7 | "assertion.same.negative", 8 | "객체 내용이 같지 않을 것으로 기대함.\n실제값:\n%s\n기대하지 않은 값:\n%s" 9 | ) 10 | 11 | s:set("assertion.equals.positive", "객체가 같을 것으로 기대함.\n실제값:\n%s\n기대값:\n%s") 12 | s:set( 13 | "assertion.equals.negative", 14 | "객체가 같지 않을 것으로 기대함.\n실제값:\n%s\n기대하지 않은 값:\n%s" 15 | ) 16 | 17 | s:set("assertion.near.positive", "값이 가까울 것으로 기대함.\n실제값:\n%s\n기대값:\n%s +/- %s") 18 | s:set( 19 | "assertion.near.negative", 20 | "값이 가깝지 않을 것으로 기대함.\n실제값:\n%s\n기대하지 않은 값:\n%s +/- %s" 21 | ) 22 | 23 | s:set("assertion.matches.positive", "문자열이 일치할 것으로 기대함.\n실제값:\n%s\n기대값:\n%s") 24 | s:set( 25 | "assertion.matches.negative", 26 | "문자열이 일치하지 않을 것으로 기대함.\n실제값:\n%s\n기대하지 않은 값:\n%s" 27 | ) 28 | 29 | s:set("assertion.unique.positive", "객체가 고유할 것으로 기대함:\n%s") 30 | s:set("assertion.unique.negative", "객체가 고유하지 않을 것으로 기대함:\n%s") 31 | 32 | s:set("assertion.error.positive", "에러를 기대함.\n발생:\n%s\n기대값:\n%s") 33 | s:set("assertion.error.negative", "에러를 기대하지 않음. 발생:\n%s") 34 | 35 | s:set("assertion.truthy.positive", "참에 준하는 값을 기대함. 실제값:\n%s") 36 | s:set("assertion.truthy.negative", "참에 준하지 않는 값을 기대함. 실제값:\n%s") 37 | 38 | s:set("assertion.falsy.positive", "거짓에 준하는 값을 기대함. 실제값:\n%s") 39 | s:set("assertion.falsy.negative", "거짓에 준하지 않는 값을 기대함. 실제값:\n%s") 40 | 41 | s:set("assertion.called.positive", "%s번 호출될 것으로 기대함. %s번 호출됨.") 42 | s:set("assertion.called.negative", "정확히 %s번 호출되면 안되지만 호출됨.") 43 | 44 | s:set("assertion.called_at_least.positive", "적어도 %s번 호출될 것으로 기대함. %s번 호출됨.") 45 | s:set("assertion.called_at_most.positive", "많아도 %s번 호출될 것으로 기대함. %s번 호출됨.") 46 | s:set("assertion.called_more_than.positive", "%s번 초과로 호출될 것으로 기대함. %s번 호출됨.") 47 | s:set("assertion.called_less_than.positive", "%s번 미만으로 호출될 것으로 기대함. %s번 호출됨.") 48 | 49 | s:set( 50 | "assertion.called_with.positive", 51 | "함수가 기대한 인자와 일치하는 호출이 없습니다.\n(있다면) 마지막 호출:\n%s\n기대값:\n%s" 52 | ) 53 | s:set( 54 | "assertion.called_with.negative", 55 | "함수가 기대하지 않은 인자와 일치하는 호출이 한 번 이상 있습니다.\n(마지막) 일치 호출:\n%s\n기대하지 않은 값:\n%s" 56 | ) 57 | 58 | s:set( 59 | "assertion.returned_with.positive", 60 | "함수가 기대한 인자와 일치하는 반환값이 없습니다.\n(있다면) 마지막 반환값:\n%s\n기대값:\n%s" 61 | ) 62 | s:set( 63 | "assertion.returned_with.negative", 64 | "함수가 기대하지 않은 인자와 일치하는 반환값이 있습니다.\n(마지막) 일치 반환값:\n%s\n기대하지 않은 값:\n%s" 65 | ) 66 | 67 | s:set("assertion.returned_arguments.positive", "%s 인자로 호출될 것으로 기대함. %s 인자로 호출됨.") 68 | s:set( 69 | "assertion.returned_arguments.negative", 70 | "%s 인자로 호출되지 않을 것으로 기대함. %s 인자로 호출됨." 71 | ) 72 | 73 | -- errors 74 | s:set( 75 | "assertion.internal.argtolittle", 76 | "'%s' 함수는 최소 %s개의 인자를 필요로 합니다. 받은 인자: %s" 77 | ) 78 | s:set("assertion.internal.badargtype", "인자 #%s 가 '%s' 함수에 주어짐. (기대값: %s, 실제값: %s)") 79 | -------------------------------------------------------------------------------- /src/languages/nl.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace('nl') 4 | 5 | s:set("assertion.same.positive", "Verwachtte objecten die vergelijkbaar zijn.\nAangeboden:\n%s\nVerwachtte:\n%s") 6 | s:set("assertion.same.negative", "Verwachtte objecten die niet vergelijkbaar zijn.\nAangeboden:\n%s\nVerwachtte niet:\n%s") 7 | 8 | s:set("assertion.equals.positive", "Verwachtte objecten die hetzelfde zijn.\nAangeboden:\n%s\nVerwachtte:\n%s") 9 | s:set("assertion.equals.negative", "Verwachtte objecten die niet hetzelfde zijn.\nAangeboden:\n%s\nVerwachtte niet:\n%s") 10 | 11 | s:set("assertion.unique.positive", "Verwachtte objecten die uniek zijn:\n%s") 12 | s:set("assertion.unique.negative", "Verwachtte objecten die niet uniek zijn:\n%s") 13 | 14 | s:set("assertion.error.positive", "Verwachtte een foutmelding.") 15 | s:set("assertion.error.negative", "Verwachtte geen foutmelding.\n%s") 16 | 17 | s:set("assertion.truthy.positive", "Verwachtte een 'warige' (thruthy) waarde, maar was:\n%s") 18 | s:set("assertion.truthy.negative", "Verwachtte een niet 'warige' (thruthy) waarde, maar was:\n%s") 19 | 20 | s:set("assertion.falsy.positive", "Verwachtte een 'onwarige' (falsy) waarde, maar was:\n%s") 21 | s:set("assertion.falsy.negative", "Verwachtte een niet 'onwarige' (falsy) waarde, maar was:\n%s") 22 | 23 | -- errors 24 | s:set("assertion.internal.argtolittle", "de '%s' functie verwacht minimaal %s parameters, maar kreeg er: %s") 25 | s:set("assertion.internal.badargtype", "bad argument #%s: de '%s' functie verwacht een %s als parameter, maar kreeg een: %s") 26 | -------------------------------------------------------------------------------- /src/languages/ru.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace("ru") 4 | 5 | s:set("assertion.same.positive", "Ожидали одинаковые объекты.\nПередали:\n%s\nОжидали:\n%s") 6 | s:set("assertion.same.negative", "Ожидали разные объекты.\nПередали:\n%s\nНе ожидали:\n%s") 7 | 8 | s:set("assertion.equals.positive", "Ожидали эквивалентные объекты.\nПередали:\n%s\nОжидали:\n%s") 9 | s:set("assertion.equals.negative", "Ожидали не эквивалентные объекты.\nПередали:\n%s\nНе ожидали:\n%s") 10 | 11 | s:set("assertion.unique.positive", "Ожидали, что объект будет уникальным:\n%s") 12 | s:set("assertion.unique.negative", "Ожидали, что объект не будет уникальным:\n%s") 13 | 14 | s:set("assertion.error.positive", "Ожидали ошибку.") 15 | s:set("assertion.error.negative", "Не ожидали ошибку.\n%s") 16 | 17 | s:set("assertion.truthy.positive", "Ожидали true, но значние оказалось:\n%s") 18 | s:set("assertion.truthy.negative", "Ожидали не true, но значние оказалось:\n%s") 19 | 20 | s:set("assertion.falsy.positive", "Ожидали false, но значние оказалось:\n%s") 21 | s:set("assertion.falsy.negative", "Ожидали не false, но значние оказалось:\n%s") 22 | -------------------------------------------------------------------------------- /src/languages/ua.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace("ua") 4 | 5 | s:set("assertion.same.positive", "Очікували однакові обєкти.\nПередали:\n%s\nОчікували:\n%s") 6 | s:set("assertion.same.negative", "Очікували різні обєкти.\nПередали:\n%s\nНе очікували:\n%s") 7 | 8 | s:set("assertion.equals.positive", "Очікували еквівалентні обєкти.\nПередали:\n%s\nОчікували:\n%s") 9 | s:set("assertion.equals.negative", "Очікували не еквівалентні обєкти.\nПередали:\n%s\nНе очікували:\n%s") 10 | 11 | s:set("assertion.unique.positive", "Очікували, що обєкт буде унікальним:\n%s") 12 | s:set("assertion.unique.negative", "Очікували, що обєкт не буде унікальним:\n%s") 13 | 14 | s:set("assertion.error.positive", "Очікували помилку.") 15 | s:set("assertion.error.negative", "Не очікували помилку.\n%s") 16 | 17 | s:set("assertion.truthy.positive", "Очікували true, проте значння виявилось:\n%s") 18 | s:set("assertion.truthy.negative", "Очікували не true, проте значння виявилось:\n%s") 19 | 20 | s:set("assertion.falsy.positive", "Очікували false, проте значння виявилось:\n%s") 21 | s:set("assertion.falsy.negative", "Очікували не false, проте значння виявилось:\n%s") 22 | -------------------------------------------------------------------------------- /src/languages/zh.lua: -------------------------------------------------------------------------------- 1 | local s = require('say') 2 | 3 | s:set_namespace('zh') 4 | 5 | s:set("assertion.same.positive", "希望对象应该相同.\n实际值:\n%s\n希望值:\n%s") 6 | s:set("assertion.same.negative", "希望对象应该不相同.\n实际值:\n%s\n不希望与:\n%s\n相同") 7 | 8 | s:set("assertion.equals.positive", "希望对象应该相等.\n实际值:\n%s\n希望值:\n%s") 9 | s:set("assertion.equals.negative", "希望对象应该不相等.\n实际值:\n%s\n不希望等于:\n%s") 10 | 11 | s:set("assertion.unique.positive", "希望对象是唯一的:\n%s") 12 | s:set("assertion.unique.negative", "希望对象不是唯一的:\n%s") 13 | 14 | s:set("assertion.error.positive", "希望有错误被抛出.") 15 | s:set("assertion.error.negative", "希望没有错误被抛出.\n%s") 16 | 17 | s:set("assertion.truthy.positive", "希望结果为真,但是实际为:\n%s") 18 | s:set("assertion.truthy.negative", "希望结果不为真,但是实际为:\n%s") 19 | 20 | s:set("assertion.falsy.positive", "希望结果为假,但是实际为:\n%s") 21 | s:set("assertion.falsy.negative", "希望结果不为假,但是实际为:\n%s") 22 | 23 | s:set("assertion.called.positive", "希望被调用%s次, 但实际被调用了%s次") 24 | s:set("assertion.called.negative", "不希望正好被调用%s次, 但是正好被调用了那么多次.") 25 | 26 | s:set("assertion.called_with.positive", "希望没有参数的调用函数") 27 | s:set("assertion.called_with.negative", "希望有参数的调用函数") 28 | 29 | -- errors 30 | s:set("assertion.internal.argtolittle", "函数'%s'需要最少%s个参数, 实际有%s个参数\n") 31 | s:set("assertion.internal.badargtype", "bad argument #%s: 函数'%s'需要一个%s作为参数, 实际为: %s\n") 32 | -------------------------------------------------------------------------------- /src/match.lua: -------------------------------------------------------------------------------- 1 | local namespace = require 'luassert.namespaces' 2 | local util = require 'luassert.util' 3 | 4 | local matcher_mt = { 5 | __call = function(self, value) 6 | return self.callback(value) == self.mod 7 | end, 8 | } 9 | 10 | local state_mt = { 11 | __call = function(self, ...) 12 | local keys = util.extract_keys("matcher", self.tokens) 13 | self.tokens = {} 14 | 15 | local matcher 16 | 17 | for _, key in ipairs(keys) do 18 | matcher = namespace.matcher[key] or matcher 19 | end 20 | 21 | if matcher then 22 | for _, key in ipairs(keys) do 23 | if namespace.modifier[key] then 24 | namespace.modifier[key].callback(self) 25 | end 26 | end 27 | 28 | local arguments = util.make_arglist(...) 29 | local matches = matcher.callback(self, arguments, util.errorlevel()) 30 | return setmetatable({ 31 | name = matcher.name, 32 | mod = self.mod, 33 | callback = matches, 34 | arguments = arguments, 35 | }, matcher_mt) 36 | else 37 | local arguments = util.make_arglist(...) 38 | 39 | for _, key in ipairs(keys) do 40 | if namespace.modifier[key] then 41 | namespace.modifier[key].callback(self, arguments, util.errorlevel()) 42 | end 43 | end 44 | end 45 | 46 | return self 47 | end, 48 | 49 | __index = function(self, key) 50 | for token in key:lower():gmatch('[^_]+') do 51 | table.insert(self.tokens, token) 52 | end 53 | 54 | return self 55 | end 56 | } 57 | 58 | local match = { 59 | _ = setmetatable({ 60 | name = "_", 61 | mod = true, 62 | callback = function() return true end, 63 | arguments = { n = 0 }, 64 | }, matcher_mt), 65 | 66 | state = function() return setmetatable({mod=true, tokens={}}, state_mt) end, 67 | 68 | is_matcher = function(object) 69 | return type(object) == "table" and getmetatable(object) == matcher_mt 70 | end, 71 | 72 | is_ref_matcher = function(object) 73 | local ismatcher = (type(object) == "table" and getmetatable(object) == matcher_mt) 74 | return ismatcher and object.name == "ref" 75 | end, 76 | } 77 | 78 | local mt = { 79 | __index = function(self, key) 80 | return rawget(self, key) or self.state()[key] 81 | end, 82 | } 83 | 84 | return setmetatable(match, mt) 85 | -------------------------------------------------------------------------------- /src/matchers/composite.lua: -------------------------------------------------------------------------------- 1 | local assert = require('luassert.assert') 2 | local match = require ('luassert.match') 3 | local s = require('say') 4 | 5 | local function none(state, arguments, level) 6 | local level = (level or 1) + 1 7 | local argcnt = arguments.n 8 | assert(argcnt > 0, s("assertion.internal.argtolittle", { "none", 1, tostring(argcnt) }), level) 9 | for i = 1, argcnt do 10 | assert(match.is_matcher(arguments[i]), s("assertion.internal.badargtype", { 1, "none", "matcher", type(arguments[i]) }), level) 11 | end 12 | 13 | return function(value) 14 | for _, matcher in ipairs(arguments) do 15 | if matcher(value) then 16 | return false 17 | end 18 | end 19 | return true 20 | end 21 | end 22 | 23 | local function any(state, arguments, level) 24 | local level = (level or 1) + 1 25 | local argcnt = arguments.n 26 | assert(argcnt > 0, s("assertion.internal.argtolittle", { "any", 1, tostring(argcnt) }), level) 27 | for i = 1, argcnt do 28 | assert(match.is_matcher(arguments[i]), s("assertion.internal.badargtype", { 1, "any", "matcher", type(arguments[i]) }), level) 29 | end 30 | 31 | return function(value) 32 | for _, matcher in ipairs(arguments) do 33 | if matcher(value) then 34 | return true 35 | end 36 | end 37 | return false 38 | end 39 | end 40 | 41 | local function all(state, arguments, level) 42 | local level = (level or 1) + 1 43 | local argcnt = arguments.n 44 | assert(argcnt > 0, s("assertion.internal.argtolittle", { "all", 1, tostring(argcnt) }), level) 45 | for i = 1, argcnt do 46 | assert(match.is_matcher(arguments[i]), s("assertion.internal.badargtype", { 1, "all", "matcher", type(arguments[i]) }), level) 47 | end 48 | 49 | return function(value) 50 | for _, matcher in ipairs(arguments) do 51 | if not matcher(value) then 52 | return false 53 | end 54 | end 55 | return true 56 | end 57 | end 58 | 59 | assert:register("matcher", "none_of", none) 60 | assert:register("matcher", "any_of", any) 61 | assert:register("matcher", "all_of", all) 62 | -------------------------------------------------------------------------------- /src/matchers/core.lua: -------------------------------------------------------------------------------- 1 | -- module will return the list of matchers, and registers matchers with the main assert engine 2 | 3 | -- matchers take 1 parameters; 4 | -- 1) state 5 | -- 2) arguments list. The list has a member 'n' with the argument count to check for trailing nils 6 | -- 3) level The level of the error position relative to the called function 7 | -- returns; function (or callable object); a function that, given an argument, returns a boolean 8 | 9 | local assert = require('luassert.assert') 10 | local astate = require('luassert.state') 11 | local util = require('luassert.util') 12 | local s = require('say') 13 | 14 | local function format(val) 15 | return astate.format_argument(val) or tostring(val) 16 | end 17 | 18 | local function unique(state, arguments, level) 19 | local deep = arguments[1] 20 | return function(value) 21 | local list = value 22 | for k,v in pairs(list) do 23 | for k2, v2 in pairs(list) do 24 | if k ~= k2 then 25 | if deep and util.deepcompare(v, v2, true) then 26 | return false 27 | else 28 | if v == v2 then 29 | return false 30 | end 31 | end 32 | end 33 | end 34 | end 35 | return true 36 | end 37 | end 38 | 39 | local function near(state, arguments, level) 40 | local level = (level or 1) + 1 41 | local argcnt = arguments.n 42 | assert(argcnt > 1, s("assertion.internal.argtolittle", { "near", 2, tostring(argcnt) }), level) 43 | local expected = tonumber(arguments[1]) 44 | local tolerance = tonumber(arguments[2]) 45 | local numbertype = "number or object convertible to a number" 46 | assert(expected, s("assertion.internal.badargtype", { 1, "near", numbertype, format(arguments[1]) }), level) 47 | assert(tolerance, s("assertion.internal.badargtype", { 2, "near", numbertype, format(arguments[2]) }), level) 48 | 49 | return function(value) 50 | local actual = tonumber(value) 51 | if not actual then return false end 52 | return (actual >= expected - tolerance and actual <= expected + tolerance) 53 | end 54 | end 55 | 56 | local function matches(state, arguments, level) 57 | local level = (level or 1) + 1 58 | local argcnt = arguments.n 59 | assert(argcnt > 0, s("assertion.internal.argtolittle", { "matches", 1, tostring(argcnt) }), level) 60 | local pattern = arguments[1] 61 | local init = arguments[2] 62 | local plain = arguments[3] 63 | assert(type(pattern) == "string", s("assertion.internal.badargtype", { 1, "matches", "string", type(arguments[1]) }), level) 64 | assert(init == nil or tonumber(init), s("assertion.internal.badargtype", { 2, "matches", "number", type(arguments[2]) }), level) 65 | 66 | return function(value) 67 | local actualtype = type(value) 68 | local actual = nil 69 | if actualtype == "string" or actualtype == "number" or 70 | actualtype == "table" and (getmetatable(value) or {}).__tostring then 71 | actual = tostring(value) 72 | end 73 | if not actual then return false end 74 | return (actual:find(pattern, init, plain) ~= nil) 75 | end 76 | end 77 | 78 | local function equals(state, arguments, level) 79 | local level = (level or 1) + 1 80 | local argcnt = arguments.n 81 | assert(argcnt > 0, s("assertion.internal.argtolittle", { "equals", 1, tostring(argcnt) }), level) 82 | return function(value) 83 | return value == arguments[1] 84 | end 85 | end 86 | 87 | local function same(state, arguments, level) 88 | local level = (level or 1) + 1 89 | local argcnt = arguments.n 90 | assert(argcnt > 0, s("assertion.internal.argtolittle", { "same", 1, tostring(argcnt) }), level) 91 | return function(value) 92 | if type(value) == 'table' and type(arguments[1]) == 'table' then 93 | local result = util.deepcompare(value, arguments[1], true) 94 | return result 95 | end 96 | return value == arguments[1] 97 | end 98 | end 99 | 100 | local function ref(state, arguments, level) 101 | local level = (level or 1) + 1 102 | local argcnt = arguments.n 103 | local argtype = type(arguments[1]) 104 | local isobject = (argtype == "table" or argtype == "function" or argtype == "thread" or argtype == "userdata") 105 | assert(argcnt > 0, s("assertion.internal.argtolittle", { "ref", 1, tostring(argcnt) }), level) 106 | assert(isobject, s("assertion.internal.badargtype", { 1, "ref", "object", argtype }), level) 107 | return function(value) 108 | return value == arguments[1] 109 | end 110 | end 111 | 112 | local function is_true(state, arguments, level) 113 | return function(value) 114 | return value == true 115 | end 116 | end 117 | 118 | local function is_false(state, arguments, level) 119 | return function(value) 120 | return value == false 121 | end 122 | end 123 | 124 | local function truthy(state, arguments, level) 125 | return function(value) 126 | return value ~= false and value ~= nil 127 | end 128 | end 129 | 130 | local function falsy(state, arguments, level) 131 | local is_truthy = truthy(state, arguments, level) 132 | return function(value) 133 | return not is_truthy(value) 134 | end 135 | end 136 | 137 | local function is_type(state, arguments, level, etype) 138 | return function(value) 139 | return type(value) == etype 140 | end 141 | end 142 | 143 | local function is_nil(state, arguments, level) return is_type(state, arguments, level, "nil") end 144 | local function is_boolean(state, arguments, level) return is_type(state, arguments, level, "boolean") end 145 | local function is_number(state, arguments, level) return is_type(state, arguments, level, "number") end 146 | local function is_string(state, arguments, level) return is_type(state, arguments, level, "string") end 147 | local function is_table(state, arguments, level) return is_type(state, arguments, level, "table") end 148 | local function is_function(state, arguments, level) return is_type(state, arguments, level, "function") end 149 | local function is_userdata(state, arguments, level) return is_type(state, arguments, level, "userdata") end 150 | local function is_thread(state, arguments, level) return is_type(state, arguments, level, "thread") end 151 | 152 | assert:register("matcher", "true", is_true) 153 | assert:register("matcher", "false", is_false) 154 | 155 | assert:register("matcher", "nil", is_nil) 156 | assert:register("matcher", "boolean", is_boolean) 157 | assert:register("matcher", "number", is_number) 158 | assert:register("matcher", "string", is_string) 159 | assert:register("matcher", "table", is_table) 160 | assert:register("matcher", "function", is_function) 161 | assert:register("matcher", "userdata", is_userdata) 162 | assert:register("matcher", "thread", is_thread) 163 | 164 | assert:register("matcher", "ref", ref) 165 | assert:register("matcher", "same", same) 166 | assert:register("matcher", "matches", matches) 167 | assert:register("matcher", "match", matches) 168 | assert:register("matcher", "near", near) 169 | assert:register("matcher", "equals", equals) 170 | assert:register("matcher", "equal", equals) 171 | assert:register("matcher", "unique", unique) 172 | assert:register("matcher", "truthy", truthy) 173 | assert:register("matcher", "falsy", falsy) 174 | -------------------------------------------------------------------------------- /src/matchers/init.lua: -------------------------------------------------------------------------------- 1 | -- load basic machers 2 | require('luassert.matchers.core') 3 | require('luassert.matchers.composite') 4 | -------------------------------------------------------------------------------- /src/mock.lua: -------------------------------------------------------------------------------- 1 | -- module will return a mock module table, and will not register any assertions 2 | local spy = require 'luassert.spy' 3 | local stub = require 'luassert.stub' 4 | 5 | local function mock_apply(object, action) 6 | if type(object) ~= "table" then return end 7 | if spy.is_spy(object) then 8 | return object[action](object) 9 | end 10 | for k,v in pairs(object) do 11 | mock_apply(v, action) 12 | end 13 | return object 14 | end 15 | 16 | local mock 17 | mock = { 18 | new = function(object, dostub, func, self, key) 19 | local visited = {} 20 | local function do_mock(object, self, key) 21 | local mock_handlers = { 22 | ["table"] = function() 23 | if spy.is_spy(object) or visited[object] then return end 24 | visited[object] = true 25 | for k,v in pairs(object) do 26 | object[k] = do_mock(v, object, k) 27 | end 28 | return object 29 | end, 30 | ["function"] = function() 31 | if dostub then 32 | return stub(self, key, func) 33 | elseif self==nil then 34 | return spy.new(object) 35 | else 36 | return spy.on(self, key) 37 | end 38 | end 39 | } 40 | local handler = mock_handlers[type(object)] 41 | return handler and handler() or object 42 | end 43 | return do_mock(object, self, key) 44 | end, 45 | 46 | clear = function(object) 47 | return mock_apply(object, "clear") 48 | end, 49 | 50 | revert = function(object) 51 | return mock_apply(object, "revert") 52 | end 53 | } 54 | 55 | return setmetatable(mock, { 56 | __call = function(self, ...) 57 | -- mock originally was a function only. Now that it is a module table 58 | -- the __call method is required for backward compatibility 59 | return mock.new(...) 60 | end 61 | }) 62 | -------------------------------------------------------------------------------- /src/modifiers.lua: -------------------------------------------------------------------------------- 1 | -- module will not return anything, only register assertions/modifiers with the main assert engine 2 | local assert = require('luassert.assert') 3 | 4 | local function is(state) 5 | return state 6 | end 7 | 8 | local function is_not(state) 9 | state.mod = not state.mod 10 | return state 11 | end 12 | 13 | assert:register("modifier", "a", is) 14 | assert:register("modifier", "an", is) 15 | assert:register("modifier", "the", is) 16 | assert:register("modifier", "is", is) 17 | assert:register("modifier", "are", is) 18 | assert:register("modifier", "was", is) 19 | assert:register("modifier", "has", is) 20 | assert:register("modifier", "does", is) 21 | assert:register("modifier", "not", is_not) 22 | assert:register("modifier", "no", is_not) 23 | -------------------------------------------------------------------------------- /src/namespaces.lua: -------------------------------------------------------------------------------- 1 | -- stores the list of namespaces 2 | return {} 3 | -------------------------------------------------------------------------------- /src/spy.lua: -------------------------------------------------------------------------------- 1 | -- module will return spy table, and register its assertions with the main assert engine 2 | local assert = require('luassert.assert') 3 | local util = require('luassert.util') 4 | 5 | -- Spy metatable 6 | local spy_mt = { 7 | __call = function(self, ...) 8 | local arguments = util.make_arglist(...) 9 | table.insert(self.calls, util.copyargs(arguments)) 10 | local function get_returns(...) 11 | local returnvals = util.make_arglist(...) 12 | table.insert(self.returnvals, util.copyargs(returnvals)) 13 | return ... 14 | end 15 | return get_returns(self.callback(...)) 16 | end 17 | } 18 | 19 | local spy -- must make local before defining table, because table contents refers to the table (recursion) 20 | spy = { 21 | new = function(callback) 22 | callback = callback or function() end 23 | if not util.callable(callback) then 24 | error("Cannot spy on type '" .. type(callback) .. "', only on functions or callable elements", util.errorlevel()) 25 | end 26 | local s = setmetatable({ 27 | calls = {}, 28 | returnvals = {}, 29 | callback = callback, 30 | 31 | target_table = nil, -- these will be set when using 'spy.on' 32 | target_key = nil, 33 | 34 | revert = function(self) 35 | if not self.reverted then 36 | if self.target_table and self.target_key then 37 | self.target_table[self.target_key] = self.callback 38 | end 39 | self.reverted = true 40 | end 41 | return self.callback 42 | end, 43 | 44 | clear = function(self) 45 | self.calls = {} 46 | self.returnvals = {} 47 | return self 48 | end, 49 | 50 | called = function(self, times, compare) 51 | if times or compare then 52 | local compare = compare or function(count, expected) return count == expected end 53 | return compare(#self.calls, times), #self.calls 54 | end 55 | 56 | return (#self.calls > 0), #self.calls 57 | end, 58 | 59 | called_with = function(self, args) 60 | local last_arglist = nil 61 | if #self.calls > 0 then 62 | last_arglist = self.calls[#self.calls].vals 63 | end 64 | local matching_arglists = util.matchargs(self.calls, args) 65 | if matching_arglists ~= nil then 66 | return true, matching_arglists.vals 67 | end 68 | return false, last_arglist 69 | end, 70 | 71 | returned_with = function(self, args) 72 | local last_returnvallist = nil 73 | if #self.returnvals > 0 then 74 | last_returnvallist = self.returnvals[#self.returnvals].vals 75 | end 76 | local matching_returnvallists = util.matchargs(self.returnvals, args) 77 | if matching_returnvallists ~= nil then 78 | return true, matching_returnvallists.vals 79 | end 80 | return false, last_returnvallist 81 | end 82 | }, spy_mt) 83 | assert:add_spy(s) -- register with the current state 84 | return s 85 | end, 86 | 87 | is_spy = function(object) 88 | return type(object) == "table" and getmetatable(object) == spy_mt 89 | end, 90 | 91 | on = function(target_table, target_key) 92 | local s = spy.new(target_table[target_key]) 93 | target_table[target_key] = s 94 | -- store original data 95 | s.target_table = target_table 96 | s.target_key = target_key 97 | 98 | return s 99 | end 100 | } 101 | 102 | local function set_spy(state, arguments, level) 103 | state.payload = arguments[1] 104 | if arguments[2] ~= nil then 105 | state.failure_message = arguments[2] 106 | end 107 | end 108 | 109 | local function returned_with(state, arguments, level) 110 | local level = (level or 1) + 1 111 | local payload = rawget(state, "payload") 112 | if payload and payload.returned_with then 113 | local assertion_holds, matching_or_last_returnvallist = state.payload:returned_with(arguments) 114 | local expected_returnvallist = util.shallowcopy(arguments) 115 | util.cleararglist(arguments) 116 | util.tinsert(arguments, 1, matching_or_last_returnvallist) 117 | util.tinsert(arguments, 2, expected_returnvallist) 118 | return assertion_holds 119 | else 120 | error("'returned_with' must be chained after 'spy(aspy)'", level) 121 | end 122 | end 123 | 124 | local function called_with(state, arguments, level) 125 | local level = (level or 1) + 1 126 | local payload = rawget(state, "payload") 127 | if payload and payload.called_with then 128 | local assertion_holds, matching_or_last_arglist = state.payload:called_with(arguments) 129 | local expected_arglist = util.shallowcopy(arguments) 130 | util.cleararglist(arguments) 131 | util.tinsert(arguments, 1, matching_or_last_arglist) 132 | util.tinsert(arguments, 2, expected_arglist) 133 | return assertion_holds 134 | else 135 | error("'called_with' must be chained after 'spy(aspy)'", level) 136 | end 137 | end 138 | 139 | local function called(state, arguments, level, compare) 140 | local level = (level or 1) + 1 141 | local num_times = arguments[1] 142 | if not num_times and not state.mod then 143 | state.mod = true 144 | num_times = 0 145 | end 146 | local payload = rawget(state, "payload") 147 | if payload and type(payload) == "table" and payload.called then 148 | local result, count = state.payload:called(num_times, compare) 149 | arguments[1] = tostring(num_times or ">0") 150 | util.tinsert(arguments, 2, tostring(count)) 151 | arguments.nofmt = arguments.nofmt or {} 152 | arguments.nofmt[1] = true 153 | arguments.nofmt[2] = true 154 | return result 155 | elseif payload and type(payload) == "function" then 156 | error("When calling 'spy(aspy)', 'aspy' must not be the original function, but the spy function replacing the original", level) 157 | else 158 | error("'called' must be chained after 'spy(aspy)'", level) 159 | end 160 | end 161 | 162 | local function called_at_least(state, arguments, level) 163 | local level = (level or 1) + 1 164 | return called(state, arguments, level, function(count, expected) return count >= expected end) 165 | end 166 | 167 | local function called_at_most(state, arguments, level) 168 | local level = (level or 1) + 1 169 | return called(state, arguments, level, function(count, expected) return count <= expected end) 170 | end 171 | 172 | local function called_more_than(state, arguments, level) 173 | local level = (level or 1) + 1 174 | return called(state, arguments, level, function(count, expected) return count > expected end) 175 | end 176 | 177 | local function called_less_than(state, arguments, level) 178 | local level = (level or 1) + 1 179 | return called(state, arguments, level, function(count, expected) return count < expected end) 180 | end 181 | 182 | assert:register("modifier", "spy", set_spy) 183 | assert:register("assertion", "returned_with", returned_with, "assertion.returned_with.positive", "assertion.returned_with.negative") 184 | assert:register("assertion", "called_with", called_with, "assertion.called_with.positive", "assertion.called_with.negative") 185 | assert:register("assertion", "called", called, "assertion.called.positive", "assertion.called.negative") 186 | assert:register("assertion", "called_at_least", called_at_least, "assertion.called_at_least.positive", "assertion.called_less_than.positive") 187 | assert:register("assertion", "called_at_most", called_at_most, "assertion.called_at_most.positive", "assertion.called_more_than.positive") 188 | assert:register("assertion", "called_more_than", called_more_than, "assertion.called_more_than.positive", "assertion.called_at_most.positive") 189 | assert:register("assertion", "called_less_than", called_less_than, "assertion.called_less_than.positive", "assertion.called_at_least.positive") 190 | 191 | return setmetatable(spy, { 192 | __call = function(self, ...) 193 | return spy.new(...) 194 | end 195 | }) 196 | -------------------------------------------------------------------------------- /src/state.lua: -------------------------------------------------------------------------------- 1 | -- maintains a state of the assert engine in a linked-list fashion 2 | -- records; formatters, parameters, spies and stubs 3 | 4 | local state_mt = { 5 | __call = function(self) 6 | self:revert() 7 | end 8 | } 9 | 10 | local spies_mt = { __mode = "kv" } 11 | 12 | local nilvalue = {} -- unique ID to refer to nil values for parameters 13 | 14 | -- will hold the current state 15 | local current 16 | 17 | -- exported module table 18 | local state = {} 19 | 20 | ------------------------------------------------------ 21 | -- Reverts to a (specific) snapshot. 22 | -- @param self (optional) the snapshot to revert to. If not provided, it will revert to the last snapshot. 23 | state.revert = function(self) 24 | if not self then 25 | -- no snapshot given, so move 1 up 26 | self = current 27 | if not self.previous then 28 | -- top of list, no previous one, nothing to do 29 | return 30 | end 31 | end 32 | if getmetatable(self) ~= state_mt then error("Value provided is not a valid snapshot", 2) end 33 | 34 | if self.next then 35 | self.next:revert() 36 | end 37 | -- revert formatters in 'last' 38 | self.formatters = {} 39 | -- revert parameters in 'last' 40 | self.parameters = {} 41 | -- revert spies/stubs in 'last' 42 | for s,_ in pairs(self.spies) do 43 | self.spies[s] = nil 44 | s:revert() 45 | end 46 | setmetatable(self, nil) -- invalidate as a snapshot 47 | current = self.previous 48 | current.next = nil 49 | end 50 | 51 | ------------------------------------------------------ 52 | -- Creates a new snapshot. 53 | -- @return snapshot table 54 | state.snapshot = function() 55 | local new = setmetatable ({ 56 | formatters = {}, 57 | parameters = {}, 58 | spies = setmetatable({}, spies_mt), 59 | previous = current, 60 | revert = state.revert, 61 | }, state_mt) 62 | if current then current.next = new end 63 | current = new 64 | return current 65 | end 66 | 67 | 68 | -- FORMATTERS 69 | state.add_formatter = function(callback) 70 | table.insert(current.formatters, 1, callback) 71 | end 72 | 73 | state.remove_formatter = function(callback, s) 74 | s = s or current 75 | for i, v in ipairs(s.formatters) do 76 | if v == callback then 77 | table.remove(s.formatters, i) 78 | break 79 | end 80 | end 81 | -- wasn't found, so traverse up 1 state 82 | if s.previous then 83 | state.remove_formatter(callback, s.previous) 84 | end 85 | end 86 | 87 | state.format_argument = function(val, s, fmtargs) 88 | s = s or current 89 | for _, fmt in ipairs(s.formatters) do 90 | local valfmt = fmt(val, fmtargs) 91 | if valfmt ~= nil then return valfmt end 92 | end 93 | -- nothing found, check snapshot 1 up in list 94 | if s.previous then 95 | return state.format_argument(val, s.previous, fmtargs) 96 | end 97 | return nil -- end of list, couldn't format 98 | end 99 | 100 | 101 | -- PARAMETERS 102 | state.set_parameter = function(name, value) 103 | if value == nil then value = nilvalue end 104 | current.parameters[name] = value 105 | end 106 | 107 | state.get_parameter = function(name, s) 108 | s = s or current 109 | local val = s.parameters[name] 110 | if val == nil and s.previous then 111 | -- not found, so check 1 up in list 112 | return state.get_parameter(name, s.previous) 113 | end 114 | if val ~= nilvalue then 115 | return val 116 | end 117 | return nil 118 | end 119 | 120 | -- SPIES / STUBS 121 | state.add_spy = function(spy) 122 | current.spies[spy] = true 123 | end 124 | 125 | state.snapshot() -- create initial state 126 | 127 | return state 128 | -------------------------------------------------------------------------------- /src/stub.lua: -------------------------------------------------------------------------------- 1 | -- module will return a stub module table 2 | local assert = require 'luassert.assert' 3 | local spy = require 'luassert.spy' 4 | local util = require 'luassert.util' 5 | local unpack = util.unpack 6 | local pack = util.pack 7 | 8 | local stub = {} 9 | 10 | function stub.new(object, key, ...) 11 | if object == nil and key == nil then 12 | -- called without arguments, create a 'blank' stub 13 | object = {} 14 | key = "" 15 | end 16 | local return_values = pack(...) 17 | assert(type(object) == "table" and key ~= nil, "stub.new(): Can only create stub on a table key, call with 2 params; table, key", util.errorlevel()) 18 | assert(object[key] == nil or util.callable(object[key]), "stub.new(): The element for which to create a stub must either be callable, or be nil", util.errorlevel()) 19 | local old_elem = object[key] -- keep existing element (might be nil!) 20 | 21 | local fn = (return_values.n == 1 and util.callable(return_values[1]) and return_values[1]) 22 | local defaultfunc = fn or function() 23 | return unpack(return_values) 24 | end 25 | local oncalls = {} 26 | local callbacks = {} 27 | local stubfunc = function(...) 28 | local args = util.make_arglist(...) 29 | local match = util.matchoncalls(oncalls, args) 30 | if match then 31 | return callbacks[match](...) 32 | end 33 | return defaultfunc(...) 34 | end 35 | 36 | object[key] = stubfunc -- set the stubfunction 37 | local s = spy.on(object, key) -- create a spy on top of the stub function 38 | local spy_revert = s.revert -- keep created revert function 39 | 40 | s.revert = function(self) -- wrap revert function to restore original element 41 | if not self.reverted then 42 | spy_revert(self) 43 | object[key] = old_elem 44 | self.reverted = true 45 | end 46 | return old_elem 47 | end 48 | 49 | s.returns = function(...) 50 | local return_args = pack(...) 51 | defaultfunc = function() 52 | return unpack(return_args) 53 | end 54 | return s 55 | end 56 | 57 | s.invokes = function(func) 58 | defaultfunc = function(...) 59 | return func(...) 60 | end 61 | return s 62 | end 63 | 64 | s.by_default = { 65 | returns = s.returns, 66 | invokes = s.invokes, 67 | } 68 | 69 | s.on_call_with = function(...) 70 | local match_args = util.make_arglist(...) 71 | match_args = util.copyargs(match_args) 72 | return { 73 | returns = function(...) 74 | local return_args = pack(...) 75 | table.insert(oncalls, match_args) 76 | callbacks[match_args] = function() 77 | return unpack(return_args) 78 | end 79 | return s 80 | end, 81 | invokes = function(func) 82 | table.insert(oncalls, match_args) 83 | callbacks[match_args] = function(...) 84 | return func(...) 85 | end 86 | return s 87 | end 88 | } 89 | end 90 | 91 | return s 92 | end 93 | 94 | local function set_stub(state, arguments) 95 | state.payload = arguments[1] 96 | state.failure_message = arguments[2] 97 | end 98 | 99 | assert:register("modifier", "stub", set_stub) 100 | 101 | return setmetatable(stub, { 102 | __call = function(self, ...) 103 | -- stub originally was a function only. Now that it is a module table 104 | -- the __call method is required for backward compatibility 105 | return stub.new(...) 106 | end 107 | }) 108 | -------------------------------------------------------------------------------- /src/util.lua: -------------------------------------------------------------------------------- 1 | local util = {} 2 | local arglist_mt = {} 3 | 4 | -- have pack/unpack both respect the 'n' field 5 | local _unpack = table.unpack or unpack 6 | local unpack = function(t, i, j) return _unpack(t, i or 1, j or t.n or #t) end 7 | local pack = function(...) return { n = select("#", ...), ... } end 8 | util.pack = pack 9 | util.unpack = unpack 10 | 11 | 12 | function util.deepcompare(t1,t2,ignore_mt,cycles,thresh1,thresh2) 13 | local ty1 = type(t1) 14 | local ty2 = type(t2) 15 | -- non-table types can be directly compared 16 | if ty1 ~= 'table' or ty2 ~= 'table' then return t1 == t2 end 17 | local mt1 = debug.getmetatable(t1) 18 | local mt2 = debug.getmetatable(t2) 19 | -- would equality be determined by metatable __eq? 20 | if mt1 and mt1 == mt2 and mt1.__eq then 21 | -- then use that unless asked not to 22 | if not ignore_mt then return t1 == t2 end 23 | else -- we can skip the deep comparison below if t1 and t2 share identity 24 | if rawequal(t1, t2) then return true end 25 | end 26 | 27 | -- handle recursive tables 28 | cycles = cycles or {{},{}} 29 | thresh1, thresh2 = (thresh1 or 1), (thresh2 or 1) 30 | cycles[1][t1] = (cycles[1][t1] or 0) 31 | cycles[2][t2] = (cycles[2][t2] or 0) 32 | if cycles[1][t1] == 1 or cycles[2][t2] == 1 then 33 | thresh1 = cycles[1][t1] + 1 34 | thresh2 = cycles[2][t2] + 1 35 | end 36 | if cycles[1][t1] > thresh1 and cycles[2][t2] > thresh2 then 37 | return true 38 | end 39 | 40 | cycles[1][t1] = cycles[1][t1] + 1 41 | cycles[2][t2] = cycles[2][t2] + 1 42 | 43 | for k1,v1 in next, t1 do 44 | local v2 = t2[k1] 45 | if v2 == nil then 46 | return false, {k1} 47 | end 48 | 49 | local same, crumbs = util.deepcompare(v1,v2,nil,cycles,thresh1,thresh2) 50 | if not same then 51 | crumbs = crumbs or {} 52 | table.insert(crumbs, k1) 53 | return false, crumbs 54 | end 55 | end 56 | for k2,_ in next, t2 do 57 | -- only check whether each element has a t1 counterpart, actual comparison 58 | -- has been done in first loop above 59 | if t1[k2] == nil then return false, {k2} end 60 | end 61 | 62 | cycles[1][t1] = cycles[1][t1] - 1 63 | cycles[2][t2] = cycles[2][t2] - 1 64 | 65 | return true 66 | end 67 | 68 | function util.shallowcopy(t) 69 | if type(t) ~= "table" then return t end 70 | local copy = {} 71 | setmetatable(copy, getmetatable(t)) 72 | for k,v in next, t do 73 | copy[k] = v 74 | end 75 | return copy 76 | end 77 | 78 | function util.deepcopy(t, deepmt, cache) 79 | local spy = require 'luassert.spy' 80 | if type(t) ~= "table" then return t end 81 | local copy = {} 82 | 83 | -- handle recursive tables 84 | local cache = cache or {} 85 | if cache[t] then return cache[t] end 86 | cache[t] = copy 87 | 88 | for k,v in next, t do 89 | copy[k] = (spy.is_spy(v) and v or util.deepcopy(v, deepmt, cache)) 90 | end 91 | if deepmt then 92 | debug.setmetatable(copy, util.deepcopy(debug.getmetatable(t), false, cache)) 93 | else 94 | debug.setmetatable(copy, debug.getmetatable(t)) 95 | end 96 | return copy 97 | end 98 | 99 | ----------------------------------------------- 100 | -- Copies arguments as a list of arguments 101 | -- @param args the arguments of which to copy 102 | -- @return the copy of the arguments 103 | function util.copyargs(args) 104 | local copy = {} 105 | setmetatable(copy, getmetatable(args)) 106 | local match = require 'luassert.match' 107 | local spy = require 'luassert.spy' 108 | for k,v in pairs(args) do 109 | copy[k] = ((match.is_matcher(v) or spy.is_spy(v)) and v or util.deepcopy(v)) 110 | end 111 | return { vals = copy, refs = util.shallowcopy(args) } 112 | end 113 | 114 | ----------------------------------------------- 115 | -- Clear an arguments or return values list from a table 116 | -- @param arglist the table to clear of arguments or return values and their count 117 | -- @return No return values 118 | function util.cleararglist(arglist) 119 | for idx = arglist.n, 1, -1 do 120 | util.tremove(arglist, idx) 121 | end 122 | arglist.n = nil 123 | end 124 | 125 | ----------------------------------------------- 126 | -- Test specs against an arglist in deepcopy and refs flavours. 127 | -- @param args deepcopy arglist 128 | -- @param argsrefs refs arglist 129 | -- @param specs arguments/return values to match against args/argsrefs 130 | -- @return true if specs match args/argsrefs, false otherwise 131 | local function matcharg(args, argrefs, specs) 132 | local match = require 'luassert.match' 133 | for idx, argval in pairs(args) do 134 | local spec = specs[idx] 135 | if match.is_matcher(spec) then 136 | if match.is_ref_matcher(spec) then 137 | argval = argrefs[idx] 138 | end 139 | if not spec(argval) then 140 | return false 141 | end 142 | elseif (spec == nil or not util.deepcompare(argval, spec)) then 143 | return false 144 | end 145 | end 146 | 147 | for idx, spec in pairs(specs) do 148 | -- only check whether each element has an args counterpart, 149 | -- actual comparison has been done in first loop above 150 | local argval = args[idx] 151 | if argval == nil then 152 | -- no args counterpart, so try to compare using matcher 153 | if match.is_matcher(spec) then 154 | if not spec(argval) then 155 | return false 156 | end 157 | else 158 | return false 159 | end 160 | end 161 | end 162 | return true 163 | end 164 | 165 | ----------------------------------------------- 166 | -- Find matching arguments/return values in a saved list of 167 | -- arguments/returned values. 168 | -- @param invocations_list list of arguments/returned values to search (list of lists) 169 | -- @param specs arguments/return values to match against argslist 170 | -- @return the last matching arguments/returned values if a match is found, otherwise nil 171 | function util.matchargs(invocations_list, specs) 172 | -- Search the arguments/returned values last to first to give the 173 | -- most helpful answer possible. In the cases where you can place 174 | -- your assertions between calls to check this gives you the best 175 | -- information if no calls match. In the cases where you can't do 176 | -- that there is no good way to predict what would work best. 177 | assert(not util.is_arglist(invocations_list), "expected a list of arglist-object, got an arglist") 178 | for ii = #invocations_list, 1, -1 do 179 | local val = invocations_list[ii] 180 | if matcharg(val.vals, val.refs, specs) then 181 | return val 182 | end 183 | end 184 | return nil 185 | end 186 | 187 | ----------------------------------------------- 188 | -- Find matching oncall for an actual call. 189 | -- @param oncalls list of oncalls to search 190 | -- @param args actual call argslist to match against 191 | -- @return the first matching oncall if a match is found, otherwise nil 192 | function util.matchoncalls(oncalls, args) 193 | for _, callspecs in ipairs(oncalls) do 194 | -- This lookup is done immediately on *args* passing into the stub 195 | -- so pass *args* as both *args* and *argsref* without copying 196 | -- either. 197 | if matcharg(args, args, callspecs.vals) then 198 | return callspecs 199 | end 200 | end 201 | return nil 202 | end 203 | 204 | ----------------------------------------------- 205 | -- table.insert() replacement that respects nil values. 206 | -- The function will use table field 'n' as indicator of the 207 | -- table length, if not set, it will be added. 208 | -- @param t table into which to insert 209 | -- @param pos (optional) position in table where to insert. NOTE: not optional if you want to insert a nil-value! 210 | -- @param val value to insert 211 | -- @return No return values 212 | function util.tinsert(...) 213 | -- check optional POS value 214 | local args = {...} 215 | local c = select('#',...) 216 | local t = args[1] 217 | local pos = args[2] 218 | local val = args[3] 219 | if c < 3 then 220 | val = pos 221 | pos = nil 222 | end 223 | -- set length indicator n if not present (+1) 224 | t.n = (t.n or #t) + 1 225 | if not pos then 226 | pos = t.n 227 | elseif pos > t.n then 228 | -- out of our range 229 | t[pos] = val 230 | t.n = pos 231 | end 232 | -- shift everything up 1 pos 233 | for i = t.n, pos + 1, -1 do 234 | t[i]=t[i-1] 235 | end 236 | -- add element to be inserted 237 | t[pos] = val 238 | end 239 | ----------------------------------------------- 240 | -- table.remove() replacement that respects nil values. 241 | -- The function will use table field 'n' as indicator of the 242 | -- table length, if not set, it will be added. 243 | -- @param t table from which to remove 244 | -- @param pos (optional) position in table to remove 245 | -- @return No return values 246 | function util.tremove(t, pos) 247 | -- set length indicator n if not present (+1) 248 | t.n = t.n or #t 249 | if not pos then 250 | pos = t.n 251 | elseif pos > t.n then 252 | local removed = t[pos] 253 | -- out of our range 254 | t[pos] = nil 255 | return removed 256 | end 257 | local removed = t[pos] 258 | -- shift everything up 1 pos 259 | for i = pos, t.n do 260 | t[i]=t[i+1] 261 | end 262 | -- set size, clean last 263 | t[t.n] = nil 264 | t.n = t.n - 1 265 | return removed 266 | end 267 | 268 | ----------------------------------------------- 269 | -- Checks an element to be callable. 270 | -- The type must either be a function or have a metatable 271 | -- containing an '__call' function. 272 | -- @param object element to inspect on being callable or not 273 | -- @return boolean, true if the object is callable 274 | function util.callable(object) 275 | if type(object) == 'function' then 276 | return true 277 | end 278 | local mt = debug.getmetatable(object) 279 | if not mt then 280 | return false 281 | end 282 | return type(rawget(mt, "__call")) == "function" 283 | end 284 | 285 | ----------------------------------------------- 286 | -- Checks an element has tostring. 287 | -- The type must either be a string or have a metatable 288 | -- containing an '__tostring' function. 289 | -- @param object element to inspect on having tostring or not 290 | -- @return boolean, true if the object has tostring 291 | function util.hastostring(object) 292 | return type(object) == "string" or type(rawget(debug.getmetatable(object) or {}, "__tostring")) == "function" 293 | end 294 | 295 | ----------------------------------------------- 296 | -- Find the first level, not defined in the same file as the caller's 297 | -- code file to properly report an error. 298 | -- @param level the level to use as the caller's source file 299 | -- @return number, the level of which to report an error 300 | function util.errorlevel(level) 301 | local level = (level or 1) + 1 -- add one to get level of the caller 302 | local info = debug.getinfo(level) 303 | local source = (info or {}).source 304 | local file = source 305 | while file and (file == source or source == "=(tail call)") do 306 | level = level + 1 307 | info = debug.getinfo(level) 308 | source = (info or {}).source 309 | end 310 | if level > 1 then level = level - 1 end -- deduct call to errorlevel() itself 311 | return level 312 | end 313 | 314 | ----------------------------------------------- 315 | -- Extract modifier and namespace keys from list of tokens. 316 | -- @param nspace the namespace from which to match tokens 317 | -- @param tokens list of tokens to search for keys 318 | -- @return table, list of keys that were extracted 319 | function util.extract_keys(nspace, tokens) 320 | local namespace = require 'luassert.namespaces' 321 | 322 | -- find valid keys by coalescing tokens as needed, starting from the end 323 | local keys = {} 324 | local key = nil 325 | local i = #tokens 326 | while i > 0 do 327 | local token = tokens[i] 328 | key = key and (token .. '_' .. key) or token 329 | 330 | -- find longest matching key in the given namespace 331 | local longkey = i > 1 and (tokens[i-1] .. '_' .. key) or nil 332 | while i > 1 and longkey and namespace[nspace][longkey] do 333 | key = longkey 334 | i = i - 1 335 | token = tokens[i] 336 | longkey = (token .. '_' .. key) 337 | end 338 | 339 | if namespace.modifier[key] or namespace[nspace][key] then 340 | table.insert(keys, 1, key) 341 | key = nil 342 | end 343 | i = i - 1 344 | end 345 | 346 | -- if there's anything left we didn't recognize it 347 | if key then 348 | error("luassert: unknown modifier/" .. nspace .. ": '" .. key .."'", util.errorlevel(2)) 349 | end 350 | 351 | return keys 352 | end 353 | 354 | ----------------------------------------------- 355 | -- store argument list for return values of a function in a table. 356 | -- The table will get a metatable to identify it as an arglist 357 | function util.make_arglist(...) 358 | local arglist = { ... } 359 | arglist.n = select('#', ...) -- add values count for trailing nils 360 | return setmetatable(arglist, arglist_mt) 361 | end 362 | 363 | ----------------------------------------------- 364 | -- check a table to be an arglist type. 365 | function util.is_arglist(object) 366 | return getmetatable(object) == arglist_mt 367 | end 368 | 369 | return util 370 | --------------------------------------------------------------------------------