diff --git a/.busted b/.busted
index a1ac321..e5b6ff6 100644
--- a/.busted
+++ b/.busted
@@ -1,5 +1,7 @@
 return {
   default = {
-    helper = "./spec/helper.lua"
+    helper = "./spec/helper.lua",
+    verbose = true,
+    output = "gtest",
   },
 }
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f3a4a78
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+root                     = true
+
+[*]
+end_of_line              = lf
+insert_final_newline     = true
+trim_trailing_whitespace = true
+charset                  = utf-8
+
+[*.lua]
+indent_style             = space
+indent_size              = 2
+
+[Makefile]
+indent_style             = tab
+
+[*.bat]
+end_of_line              = crlf
diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml
new file mode 100644
index 0000000..9d6ff91
--- /dev/null
+++ b/.github/workflows/luacheck.yml
@@ -0,0 +1,21 @@
+name: Luacheck
+
+on: [push, pull_request]
+
+jobs:
+
+  luacheck:
+    runs-on: ubuntu-20.04
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v2
+    - name: Setup ‘lua’
+      uses: leafo/gh-actions-lua@v8
+      with:
+        luaVersion: 5.3
+    - name: Setup ‘luarocks’
+      uses: leafo/gh-actions-luarocks@v4
+    - name: Setup ‘luacheck’
+      run: luarocks install luacheck
+    - name: Run ‘luacheck’ linter
+      run: luacheck .
diff --git a/.github/workflows/luassert.yml b/.github/workflows/luassert.yml
new file mode 100644
index 0000000..091e90a
--- /dev/null
+++ b/.github/workflows/luassert.yml
@@ -0,0 +1,39 @@
+name: Luassert
+
+on: [push, pull_request]
+
+jobs:
+
+  busted:
+    strategy:
+      fail-fast: false
+      matrix:
+        luaVersion: ["5.4", "5.3", "5.2", "5.1", "luajit", "luajit-openresty"]
+    runs-on: ubuntu-20.04
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v2
+    - name: Setup ‘lua’
+      uses: leafo/gh-actions-lua@v8
+      with:
+        luaVersion: ${{ matrix.luaVersion }}
+    - name: Setup ‘luarocks’
+      uses: leafo/gh-actions-luarocks@v4
+    - name: Setup dependencies
+      run: |
+        luarocks install busted
+        luarocks install luacov
+        luarocks remove luassert --force
+    - name: Cache Lua machinery
+      uses: actions/cache@v2
+      with:
+        path: |
+          .install
+          .lua
+          .luarocks
+        key: lua-${{ matrix.luaVersion }}-${{ hashFiles('.github/workflows/luassert.yml') }}
+    - name: Build
+      run: |
+        luarocks make
+    - name: Run tests
+      run: busted -v
diff --git a/.luacheckrc b/.luacheckrc
new file mode 100644
index 0000000..15aae5c
--- /dev/null
+++ b/.luacheckrc
@@ -0,0 +1,32 @@
+std             = "max+busted"
+unused_args     = false
+redefined       = false
+max_line_length = false
+
+
+globals = {
+    "randomize",
+    "match",
+    "async",
+    "done",
+    "busted",
+    --"ngx.IS_CLI",
+}
+
+
+not_globals = {
+    "string.len",
+    "table.getn",
+}
+
+
+ignore = {
+    --"6.", -- ignore whitespace warnings
+}
+
+
+exclude_files = {
+    ".luarocks/**",
+    ".install/**",
+}
+
diff --git a/.travis.yml b/.travis.yml
index c4487f7..a6bc91a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,10 +15,13 @@ before_install:
   - source .travis/setenv_lua.sh
 
 install:
-  - luarocks make
   - luarocks install busted
+  - luarocks install luacheck
+  - luarocks remove luassert --force && luarocks make
 
-script: busted spec
+script:
+  - luacheck .
+  - busted
 
 branches:
   only:
diff --git a/.travis/platform.sh b/.travis/platform.sh
deleted file mode 100644
index 7259a7d..0000000
--- a/.travis/platform.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-if [ -z "${PLATFORM:-}" ]; then
-  PLATFORM=$TRAVIS_OS_NAME;
-fi
-
-if [ "$PLATFORM" == "osx" ]; then
-  PLATFORM="macosx";
-fi
-
-if [ -z "$PLATFORM" ]; then
-  if [ "$(uname)" == "Linux" ]; then
-    PLATFORM="linux";
-  else
-    PLATFORM="macosx";
-  fi;
-fi
diff --git a/.travis/setenv_lua.sh b/.travis/setenv_lua.sh
deleted file mode 100644
index 8d8c825..0000000
--- a/.travis/setenv_lua.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin:${TRAVIS_BUILD_DIR}/install/luarocks/bin
-bash .travis/setup_lua.sh
-eval `$HOME/.lua/luarocks path`
diff --git a/.travis/setup_lua.sh b/.travis/setup_lua.sh
deleted file mode 100644
index 9dc3aca..0000000
--- a/.travis/setup_lua.sh
+++ /dev/null
@@ -1,122 +0,0 @@
-#! /bin/bash
-
-# A script for setting up environment for travis-ci testing.
-# Sets up Lua and Luarocks.
-# LUA must be "lua5.1", "lua5.2", "lua5.3" or "luajit".
-# luajit2.0 - master v2.0
-# luajit2.1 - master v2.1
-
-set -eufo pipefail
-
-LUAJIT_VERSION="2.0.4"
-LUAJIT_BASE="LuaJIT-$LUAJIT_VERSION"
-
-source .travis/platform.sh
-
-LUA_HOME_DIR=$TRAVIS_BUILD_DIR/install/lua
-
-LR_HOME_DIR=$TRAVIS_BUILD_DIR/install/luarocks
-
-mkdir $HOME/.lua
-
-LUAJIT="no"
-
-if [ "$PLATFORM" == "macosx" ]; then
-  if [ "$LUA" == "luajit" ]; then
-    LUAJIT="yes";
-  fi
-  if [ "$LUA" == "luajit2.0" ]; then
-    LUAJIT="yes";
-  fi
-  if [ "$LUA" == "luajit2.1" ]; then
-    LUAJIT="yes";
-  fi;
-elif [ "$(expr substr $LUA 1 6)" == "luajit" ]; then
-  LUAJIT="yes";
-fi
-
-mkdir -p "$LUA_HOME_DIR"
-
-if [ "$LUAJIT" == "yes" ]; then
-
-  if [ "$LUA" == "luajit" ]; then
-    curl --location https://github.com/LuaJIT/LuaJIT/archive/v$LUAJIT_VERSION.tar.gz | tar xz;
-  else
-    git clone https://github.com/LuaJIT/LuaJIT.git $LUAJIT_BASE;
-  fi
-
-  cd $LUAJIT_BASE
-
-  if [ "$LUA" == "luajit2.1" ]; then
-    git checkout v2.1;
-    # force the INSTALL_TNAME to be luajit
-    perl -i -pe 's/INSTALL_TNAME=.+/INSTALL_TNAME= luajit/' Makefile
-  fi
-
-  make && make install PREFIX="$LUA_HOME_DIR"
-
-  ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/luajit
-  ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/lua;
-
-else
-
-  if [ "$LUA" == "lua5.1" ]; then
-    curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz
-    cd lua-5.1.5;
-  elif [ "$LUA" == "lua5.2" ]; then
-    curl http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz
-    cd lua-5.2.4;
-  elif [ "$LUA" == "lua5.3" ]; then
-    curl http://www.lua.org/ftp/lua-5.3.2.tar.gz | tar xz
-    cd lua-5.3.2;
-  fi
-
-  # Build Lua without backwards compatibility for testing
-  perl -i -pe 's/-DLUA_COMPAT_(ALL|5_2)//' src/Makefile
-  make $PLATFORM
-  make INSTALL_TOP="$LUA_HOME_DIR" install;
-
-  ln -s $LUA_HOME_DIR/bin/lua $HOME/.lua/lua
-  ln -s $LUA_HOME_DIR/bin/luac $HOME/.lua/luac;
-
-fi
-
-cd $TRAVIS_BUILD_DIR
-
-lua -v
-
-LUAROCKS_BASE=luarocks-$LUAROCKS
-
-curl --location http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz | tar xz
-
-cd $LUAROCKS_BASE
-
-if [ "$LUA" == "luajit" ]; then
-  ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR";
-elif [ "$LUA" == "luajit2.0" ]; then
-  ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR";
-elif [ "$LUA" == "luajit2.1" ]; then
-  ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.1" --prefix="$LR_HOME_DIR";
-else
-  ./configure --with-lua="$LUA_HOME_DIR" --prefix="$LR_HOME_DIR"
-fi
-
-make build && make install
-
-ln -s $LR_HOME_DIR/bin/luarocks $HOME/.lua/luarocks
-
-cd $TRAVIS_BUILD_DIR
-
-luarocks --version
-
-rm -rf $LUAROCKS_BASE
-
-if [ "$LUAJIT" == "yes" ]; then
-  rm -rf $LUAJIT_BASE;
-elif [ "$LUA" == "lua5.1" ]; then
-  rm -rf lua-5.1.5;
-elif [ "$LUA" == "lua5.2" ]; then
-  rm -rf lua-5.2.4;
-elif [ "$LUA" == "lua5.3" ]; then
-  rm -rf lua-5.3.2;
-fi
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..402b9c7
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,16 @@
+# Changelog
+
+### 1.8.x unreleased
+
+* `called_with` and `returned_with` asserts now try to show why they fail.
+
+  This applies to spies, stubs and mocks.
+
+  If no call matches the expected arguments or returned values are
+  compared to those of the last call.
+
+  If no call was expected to match but one or more does the expected
+  arguments or returned values are compared to the last matching call.
+
+
+### 1.8.0 released 28-Jun-2019
diff --git a/README.md b/README.md
index 04f0321..625be4d 100644
--- a/README.md
+++ b/README.md
@@ -1,282 +1,283 @@
-Luassert
-========
-
-[![Build Status](https://secure.travis-ci.org/Olivine-Labs/luassert.png)](http://secure.travis-ci.org/Olivine-Labs/luassert)
-
-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`.
-
-Check out [busted](http://www.olivinelabs.com/busted#asserts) for extended examples.
-
-```lua
-assert = require("luassert")
-
-assert.True(true)
-assert.is.True(true)
-assert.is_true(true)
-assert.is_not.True(false)
-assert.is.Not.True(false)
-assert.is_not_true(false)
-assert.are.equal(1, 1)
-assert.has.errors(function() error("this should fail") end)
-```
-
-Extend your own:
-
-```lua
-local assert = require("luassert")
-local say    = require("say") --our i18n lib, installed through luarocks, included as a luassert dependency
-
-local function has_property(state, arguments)
-  local property = arguments[1]
-  local table = arguments[2]
-  for key, value in pairs(table) do
-    if key == property then
-      return true
-    end
-  end
-  return false
-end
-
-say:set_namespace("en")
-say:set("assertion.has_property.positive", "Expected property %s in:\n%s")
-say:set("assertion.has_property.negative", "Expected property %s to not be in:\n%s")
-assert:register("assertion", "has_property", has_property, "assertion.has_property.positive", "assertion.has_property.negative")
-
-assert.has_property("name", { name = "jack" })
-
-```
-
-When writing your own assertions you can also use modifiers to set specific objects to work against. An example 
-is the [`array` modifier](https://github.com/Olivine-Labs/luassert/blob/master/src/array.lua) with its 
-accompanying `holes` assertion.
-
-Which can be used as;
-```lua
-local arr = { "one", "two", "three" }
-
-assert.array(arr).has.no.holes()   -- checks the array to not contain holes --> passes
-assert.array(arr).has.no.holes(4)  -- sets explicit length to 4 --> fails
-```
-
-## Implementation notes:
-
-* 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
-* 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
- * To specify a custom failure message for the `returned_arguments` assertion, use the `message` modifier
-```lua
-local f = function() end
-assert.message("the function 'f' did not return 2 arguments").returned_arguments(2, f())
-```
-
-## Matchers
-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.
-```lua
-local assert = require 'luassert'
-local match = require 'luassert.match'
-local spy = require 'luassert.spy'
-
-local s = spy.new(function() end)
-s('foo')
-s(1)
-s({}, 'foo')
-assert.spy(s).was.called_with(match._) -- arg1 is anything
-assert.spy(s).was.called_with(match.is_string()) -- arg1 is a string
-assert.spy(s).was.called_with(match.is_number()) -- arg1 is a number
-assert.spy(s).was.called_with(match.is_not_true()) -- arg1 is not true
-assert.spy(s).was.called_with(match.is_table(), match.is_string()) -- arg1 is a table, arg2 is a string
-assert.spy(s).was.called_with(match.has_match('.oo')) -- arg1 contains pattern ".oo"
-assert.spy(s).was.called_with({}, 'foo') -- you can still match without using matchers
-```
-Extend your own:
-```lua
-local function is_even(state, arguments)
-  return function(value)
-    return (value % 2) == 0
-  end
-end
-
-local function is_gt(state, arguments)
-  local expected = arguments[1]
-  return function(value)
-    return value > expected
-  end
-end
-
-assert:register("matcher", "even", is_even)
-assert:register("matcher", "gt", is_gt)
-```
-```lua
-local assert = require 'luassert'
-local match = require 'luassert.match'
-local spy = require 'luassert.spy'
-
-local s = spy.new(function() end)
-s(7)
-assert.spy(s).was.called_with(match.is_number()) -- arg1 was a number
-assert.spy(s).was.called_with(match.is_not_even()) -- arg1 was not even
-assert.spy(s).was.called_with(match.is_gt(5)) -- arg1 was greater than 5
-```
-Composite matchers have the form:
-```lua
-match.all_of(m1, m2, ...) -- argument matches all of the matchers m1 to mn
-match.any_of(m1, m2, ...) -- argument matches at least one of the matchers m1 to mn
-match.none_of(m1, m2, ...) -- argument does not match any of the matchers m1 to mn
-```
-
-If you're creating a spy for methods that mutate any properties on `self` you should should use `match.is_ref(obj)`:
-```lua
-local t = { cnt = 0, }
-function t:incrby(i) self.cnt = self.cnt + i end
-
-local s = spy.on(t, "incrby")
-t:incrby(2)
-
-assert.spy(s).was_called_with(match.is_ref(t), 2)
-```
-
-## Snapshots
-To be able to revert changes created by tests, inserting spies and stubs for example, luassert supports 'snapshots'. A snapshot includes the following;
-
-1. spies and stubs
-1. parameters
-1. formatters
-
-Example:
-```lua
-describe("Showing use of snapshots", function()
-  local snapshot
-
-  before_each(function()
-    snapshot = assert:snapshot()
-  end)
-
-  after_each(function()
-    snapshot:revert()
-  end)
-
-  it("does some test", function()
-    -- spies or stubs registered here, parameters changed, or formatters added
-    -- will be undone in the after_each() handler.
-  end)
-
-end)
-```
-
-## Parameters
-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.
-
-Example:
-```lua
-assert:set_parameter("my_param_name", 1)
-local s = assert:snapshot()
-assert:set_parameter("my_param_name", 2)
-s:revert()
-assert.are.equal(1, assert:get_parameter("my_param_name"))
-```
-
-## Customizing argument formatting
-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.
-
-### Configuring table depth display
-The default table formatter allows you to customize the levels displayed by setting the `TableFormatLevel` parameter (setting it to -1 displays all levels).
-
-Example:
-```lua
-describe("Tests different levels of table display", function()
-
-  local testtable = {
-      hello = "hola",
-      world = "mundo",
-      liqour = {
-          "beer", "wine", "water"
-        },
-      fruit = {
-          native = { "apple", "strawberry", "grape" },
-          tropical = { "banana", "orange", "mango" },
-        },
-    }
-
-  it("tests display of 0 levels", function()
-    assert:set_parameter("TableFormatLevel", 0)
-    assert.are.same(testtable, {})
-  end)
-
-  it("tests display of 2 levels", function()
-    assert:set_parameter("TableFormatLevel", 2)
-    assert.are.same(testtable, {})
-  end)
-
-end)
-```
-
-Will display the following output with the table pretty-printed to the requested depth:
-```
-Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 45
-tests display of 0 levels
-...ua projects\busted\formatter\spec\formatter_spec.lua:47: Expected objects to be the same. Passed in:
-(table): { }
-Expected:
-(table): { ... more }
-
-Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 50
-tests display of 2 levels
-...ua projects\busted\formatter\spec\formatter_spec.lua:52: Expected objects to be the same. Passed in:
-(table): { }
-Expected:
-(table): {
-  [hello] = 'hola'
-  [fruit] = {
-    [tropical] = { ... more }
-    [native] = { ... more } }
-  [liqour] = {
-    [1] = 'beer'
-    [2] = 'wine'
-    [3] = 'water' }
-  [world] = 'mundo' }
-```
-### Customized formatters
-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`.
-Formatters can be added through `assert:add_formatter(formatter_func)`, and removed by calling `assert:remove_formatter(formatter_func)`.
-
-Example using the included binary string formatter:
-```lua
-local binstring = require("luassert.formatters.binarystring")
-
-describe("Tests using a binary string formatter", function()
-
-  setup(function()
-    assert:add_formatter(binstring)
-  end)
-
-  teardown(function()
-    assert:remove_formatter(binstring)
-  end)
-
-  it("tests a string comparison with binary formatting", function()
-    local s1, s2 = "", ""
-    for n = 65,88 do
-      s1 = s1 .. string.char(n)
-      s2 = string.char(n) .. s2
-    end
-    assert.are.same(s1, s2)
-
-  end)
-
-end)
-```
-
-Because this formatter formats string values, and is added last, it will take precedence over the regular string formatter. The results will be:
-```
-Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 13
-tests a string comparison with binary formatting
-...ua projects\busted\formatter\spec\formatter_spec.lua:19: Expected objects to be the same. Passed in:
-Binary string length; 24 bytes
-58 57 56 55 54 53 52 51   50 4f 4e 4d 4c 4b 4a 49  XWVUTSRQ PONMLKJI
-48 47 46 45 44 43 42 41                            HGFEDCBA
-
-Expected:
-Binary string length; 24 bytes
-41 42 43 44 45 46 47 48   49 4a 4b 4c 4d 4e 4f 50  ABCDEFGH IJKLMNOP
-51 52 53 54 55 56 57 58                            QRSTUVWX
-```
-
+Luassert
+========
+
+[![Luacheck Lint Status](https://img.shields.io/github/workflow/status/Olivine-Labs/luassert/Luacheck?label=Luacheck&logo=Lua)](https://github.com/Olivine-Labs/luassert/actions?workflow=Luacheck)
+[![Luassert Test Status](https://img.shields.io/github/workflow/status/Olivine-Labs/luassert/Luassert?label=Linux%20Build&logo=Github)](https://github.com/Olivine-Labs/luassert/actions?workflow=Luassert)
+
+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`.
+
+Check out [busted](http://www.olivinelabs.com/busted#asserts) for extended examples.
+
+```lua
+assert = require("luassert")
+
+assert.True(true)
+assert.is.True(true)
+assert.is_true(true)
+assert.is_not.True(false)
+assert.is.Not.True(false)
+assert.is_not_true(false)
+assert.are.equal(1, 1)
+assert.has.errors(function() error("this should fail") end)
+```
+
+Extend your own:
+
+```lua
+local assert = require("luassert")
+local say    = require("say") --our i18n lib, installed through luarocks, included as a luassert dependency
+
+local function has_property(state, arguments)
+  local property = arguments[1]
+  local table = arguments[2]
+  for key, value in pairs(table) do
+    if key == property then
+      return true
+    end
+  end
+  return false
+end
+
+say:set_namespace("en")
+say:set("assertion.has_property.positive", "Expected property %s in:\n%s")
+say:set("assertion.has_property.negative", "Expected property %s to not be in:\n%s")
+assert:register("assertion", "has_property", has_property, "assertion.has_property.positive", "assertion.has_property.negative")
+
+assert.has_property("name", { name = "jack" })
+
+```
+
+When writing your own assertions you can also use modifiers to set specific objects to work against. An example
+is the [`array` modifier](https://github.com/Olivine-Labs/luassert/blob/master/src/array.lua) with its
+accompanying `holes` assertion.
+
+Which can be used as;
+```lua
+local arr = { "one", "two", "three" }
+
+assert.array(arr).has.no.holes()   -- checks the array to not contain holes --> passes
+assert.array(arr).has.no.holes(4)  -- sets explicit length to 4 --> fails
+```
+
+## Implementation notes:
+
+* 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
+* 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
+ * To specify a custom failure message for the `returned_arguments` assertion, use the `message` modifier
+```lua
+local f = function() end
+assert.message("the function 'f' did not return 2 arguments").returned_arguments(2, f())
+```
+
+## Matchers
+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.
+```lua
+local assert = require 'luassert'
+local match = require 'luassert.match'
+local spy = require 'luassert.spy'
+
+local s = spy.new(function() end)
+s('foo')
+s(1)
+s({}, 'foo')
+assert.spy(s).was.called_with(match._) -- arg1 is anything
+assert.spy(s).was.called_with(match.is_string()) -- arg1 is a string
+assert.spy(s).was.called_with(match.is_number()) -- arg1 is a number
+assert.spy(s).was.called_with(match.is_not_true()) -- arg1 is not true
+assert.spy(s).was.called_with(match.is_table(), match.is_string()) -- arg1 is a table, arg2 is a string
+assert.spy(s).was.called_with(match.has_match('.oo')) -- arg1 contains pattern ".oo"
+assert.spy(s).was.called_with({}, 'foo') -- you can still match without using matchers
+```
+Extend your own:
+```lua
+local function is_even(state, arguments)
+  return function(value)
+    return (value % 2) == 0
+  end
+end
+
+local function is_gt(state, arguments)
+  local expected = arguments[1]
+  return function(value)
+    return value > expected
+  end
+end
+
+assert:register("matcher", "even", is_even)
+assert:register("matcher", "gt", is_gt)
+```
+```lua
+local assert = require 'luassert'
+local match = require 'luassert.match'
+local spy = require 'luassert.spy'
+
+local s = spy.new(function() end)
+s(7)
+assert.spy(s).was.called_with(match.is_number()) -- arg1 was a number
+assert.spy(s).was.called_with(match.is_not_even()) -- arg1 was not even
+assert.spy(s).was.called_with(match.is_gt(5)) -- arg1 was greater than 5
+```
+Composite matchers have the form:
+```lua
+match.all_of(m1, m2, ...) -- argument matches all of the matchers m1 to mn
+match.any_of(m1, m2, ...) -- argument matches at least one of the matchers m1 to mn
+match.none_of(m1, m2, ...) -- argument does not match any of the matchers m1 to mn
+```
+
+If you're creating a spy for methods that mutate any properties on `self` you should should use `match.is_ref(obj)`:
+```lua
+local t = { cnt = 0, }
+function t:incrby(i) self.cnt = self.cnt + i end
+
+local s = spy.on(t, "incrby")
+t:incrby(2)
+
+assert.spy(s).was_called_with(match.is_ref(t), 2)
+```
+
+## Snapshots
+To be able to revert changes created by tests, inserting spies and stubs for example, luassert supports 'snapshots'. A snapshot includes the following;
+
+1. spies and stubs
+1. parameters
+1. formatters
+
+Example:
+```lua
+describe("Showing use of snapshots", function()
+  local snapshot
+
+  before_each(function()
+    snapshot = assert:snapshot()
+  end)
+
+  after_each(function()
+    snapshot:revert()
+  end)
+
+  it("does some test", function()
+    -- spies or stubs registered here, parameters changed, or formatters added
+    -- will be undone in the after_each() handler.
+  end)
+
+end)
+```
+
+## Parameters
+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.
+
+Example:
+```lua
+assert:set_parameter("my_param_name", 1)
+local s = assert:snapshot()
+assert:set_parameter("my_param_name", 2)
+s:revert()
+assert.are.equal(1, assert:get_parameter("my_param_name"))
+```
+
+## Customizing argument formatting
+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.
+
+### Configuring table depth display
+The default table formatter allows you to customize the levels displayed by setting the `TableFormatLevel` parameter (setting it to -1 displays all levels).
+
+Example:
+```lua
+describe("Tests different levels of table display", function()
+
+  local testtable = {
+      hello = "hola",
+      world = "mundo",
+      liqour = {
+          "beer", "wine", "water"
+        },
+      fruit = {
+          native = { "apple", "strawberry", "grape" },
+          tropical = { "banana", "orange", "mango" },
+        },
+    }
+
+  it("tests display of 0 levels", function()
+    assert:set_parameter("TableFormatLevel", 0)
+    assert.are.same(testtable, {})
+  end)
+
+  it("tests display of 2 levels", function()
+    assert:set_parameter("TableFormatLevel", 2)
+    assert.are.same(testtable, {})
+  end)
+
+end)
+```
+
+Will display the following output with the table pretty-printed to the requested depth:
+```
+Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 45
+tests display of 0 levels
+...ua projects\busted\formatter\spec\formatter_spec.lua:47: Expected objects to be the same. Passed in:
+(table): { }
+Expected:
+(table): { ... more }
+
+Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 50
+tests display of 2 levels
+...ua projects\busted\formatter\spec\formatter_spec.lua:52: Expected objects to be the same. Passed in:
+(table): { }
+Expected:
+(table): {
+  [hello] = 'hola'
+  [fruit] = {
+    [tropical] = { ... more }
+    [native] = { ... more } }
+  [liqour] = {
+    [1] = 'beer'
+    [2] = 'wine'
+    [3] = 'water' }
+  [world] = 'mundo' }
+```
+### Customized formatters
+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`.
+Formatters can be added through `assert:add_formatter(formatter_func)`, and removed by calling `assert:remove_formatter(formatter_func)`.
+
+Example using the included binary string formatter:
+```lua
+local binstring = require("luassert.formatters.binarystring")
+
+describe("Tests using a binary string formatter", function()
+
+  setup(function()
+    assert:add_formatter(binstring)
+  end)
+
+  teardown(function()
+    assert:remove_formatter(binstring)
+  end)
+
+  it("tests a string comparison with binary formatting", function()
+    local s1, s2 = "", ""
+    for n = 65,88 do
+      s1 = s1 .. string.char(n)
+      s2 = string.char(n) .. s2
+    end
+    assert.are.same(s1, s2)
+
+  end)
+
+end)
+```
+
+Because this formatter formats string values, and is added last, it will take precedence over the regular string formatter. The results will be:
+```
+Failure: ...ua projects\busted\formatter\spec\formatter_spec.lua @ 13
+tests a string comparison with binary formatting
+...ua projects\busted\formatter\spec\formatter_spec.lua:19: Expected objects to be the same. Passed in:
+Binary string length; 24 bytes
+58 57 56 55 54 53 52 51   50 4f 4e 4d 4c 4b 4a 49  XWVUTSRQ PONMLKJI
+48 47 46 45 44 43 42 41                            HGFEDCBA
+
+Expected:
+Binary string length; 24 bytes
+41 42 43 44 45 46 47 48   49 4a 4b 4c 4d 4e 4f 50  ABCDEFGH IJKLMNOP
+51 52 53 54 55 56 57 58                            QRSTUVWX
+```
+
diff --git a/debian/changelog b/debian/changelog
index ae2cc08..0c9a455 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+lua-luassert (1.8.0+git20220201.1.e2ab0d2-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 07 May 2022 05:08:31 -0000
+
 lua-luassert (1.8.0-2) unstable; urgency=medium
 
   [ Kim Alvefur ]
diff --git a/luassert-1.8.0-0.rockspec b/luassert-scm-1.rockspec
similarity index 93%
rename from luassert-1.8.0-0.rockspec
rename to luassert-scm-1.rockspec
index fe6d677..7ea9645 100644
--- a/luassert-1.8.0-0.rockspec
+++ b/luassert-scm-1.rockspec
@@ -1,8 +1,9 @@
 package = "luassert"
-version = "1.8.0-0"
+version = "scm-1"
 source = {
-  url = "https://github.com/Olivine-Labs/luassert/archive/v1.8.0.tar.gz",
-  dir = "luassert-1.8.0"
+  url = "git://github.com/Olivine-Labs/luassert.git",
+  --dir = "luassert-1.8.0"
+  tag = "master"
 }
 description = {
   summary = "Lua Assertions Extension",
diff --git a/spec/assertions_spec.lua b/spec/assertions_spec.lua
index 6ca8fdc..7690ab8 100644
--- a/spec/assertions_spec.lua
+++ b/spec/assertions_spec.lua
@@ -6,12 +6,12 @@ describe("Test Assertions", function()
     local message = "the message"
     local third_arg = "three"
     local fourth_arg = "four"
-    one, two, three, four, five = assert(test, message, third_arg, fourth_arg)
+    local one, two, three, four, five = assert(test, message, third_arg, fourth_arg)
     assert(one == test and two == message and three == third_arg and
            four == fourth_arg and five == nil,
            "Expected input values to be outputted as well when an assertion does not fail")
   end)
-  
+
   it("Checks assert() handles more than two return values", function()
     local res, err = pcall(assert, false, "some error", "a string")
     assert(not res)
@@ -27,7 +27,7 @@ describe("Test Assertions", function()
     assert.is.Nil(assert:get_level("hello world"))
     assert.is.Nil(assert:get_level(nil))
   end)
-  
+
   it("Checks asserts can be reused", function()
     local is_same = assert.is_same
     local orig_same = tablex.deepcopy(is_same)
@@ -484,7 +484,6 @@ describe("Test Assertions", function()
   end)
 
   it("Checks error_matches does not compare error objects", function()
-    local func = function() end
     assert.no_error_matches(function() error({ "table" }) end, "table")
   end)
 
diff --git a/spec/formatters_spec.lua b/spec/formatters_spec.lua
index 3ac5962..1eada39 100644
--- a/spec/formatters_spec.lua
+++ b/spec/formatters_spec.lua
@@ -1,7 +1,8 @@
+local util = require("luassert.util")
+
 local function returnnils()
   -- force the return of nils in an argument array
-  local a,b
-  return a, b, "this is not nil"
+  return nil, nil, "this is not nil"
 end
 
 describe("Test Formatters", function()
@@ -140,13 +141,31 @@ describe("Test Formatters", function()
 
   it("Checks to see if table containing nils is returned with same number of entries #test", function()
     local t = { returnnils(), ["n"] = 3 }
-    formatted = assert:format(t)
+    local formatted = assert:format(t)
     assert.is.same(type(formatted[1]), "string")
     assert.is.same(type(formatted[2]), "string")
     assert.is.same(type(formatted[3]), "string")
     assert.is.same(type(formatted[4]), "nil")
   end)
 
+  it("checks matcher formatting", function()
+    local pattern = match.match("pattern")
+    local patternformatted = assert:format({ pattern, ["n"] = 1 })[1]
+    assert.is.same("(matcher) is.match((string) 'pattern')",
+                   patternformatted)
+    local nostring = match.no.string()
+    local nostringformatted = assert:format({nostring, ["n"] = 1})[1]
+    assert.is.same("(matcher) no.string()",
+                   nostringformatted)
+  end)
+
+  it("checks arglist formatting", function()
+    local arglist = util.make_arglist("word", nil, 4, nil)
+    local formatted = assert:format({ arglist, ["n"] = 1 })[1]
+    assert.is.same(formatted,
+                   "(values list) ((string) 'word', (nil), (number) 4, (nil))")
+  end)
+
   it("checks arguments not being formatted if set to do so", function()
     local arg1 = "argument1"
     local arg2 = "argument2"
diff --git a/spec/helper.lua b/spec/helper.lua
index 3160995..1823ce9 100644
--- a/spec/helper.lua
+++ b/spec/helper.lua
@@ -1,5 +1,5 @@
 -- busted helper file to prevent crashes on LuaJIT ffi module being
--- garbage collected due to Busted cleaning up the test enviornment
+-- garbage collected due to Busted cleaning up the test environment
 --
 -- usage:
 --   busted --helper=spec/helper.lua
diff --git a/spec/mocks_spec.lua b/spec/mocks_spec.lua
index d63104c..27d399a 100644
--- a/spec/mocks_spec.lua
+++ b/spec/mocks_spec.lua
@@ -57,4 +57,30 @@ describe("Tests dealing with mocks", function()
     mock(test)
     assert(test.foo == test)
   end)
+
+  it("should try to show why called_with fails", function()
+    mock(test)
+    test.key()
+    assert.error_matches(
+      function () assert.spy(test.key).was.called_with(5) end,
+      "Function was never called with matching arguments.\n"
+      .. "Called with (last call if any):\n"
+      .. "(values list) ()\n"
+      .. "Expected:\n"
+      .. "(values list) ((number) 5)",
+      1, true)
+  end)
+
+  it("should try to show why returned_with fails", function()
+    mock(test)
+    test.key()
+    assert.error_matches(
+      function () assert.spy(test.key).returned_with(5) end,
+      "Function never returned matching arguments.\n"
+      .. "Returned (last call if any):\n"
+      .. "(values list) ((string) 'derp')\n"
+      .. "Expected:\n"
+      .. "(values list) ((number) 5)",
+      1, true)
+  end)
 end)
diff --git a/spec/output_spec.lua b/spec/output_spec.lua
index 57a72a5..b44b9e1 100644
--- a/spec/output_spec.lua
+++ b/spec/output_spec.lua
@@ -1,8 +1,8 @@
-local unpack = require 'luassert.compatibility'.unpack
+local unpack = require 'luassert.util'.unpack
 
 describe("Output testing using string comparison with the equal assertion", function()
   local getoutput = function(...)
-    local success, message = pcall(assert.are.equal, ...)
+    local _, message = pcall(assert.are.equal, ...)
     if message == nil then return nil end
     return tostring(message)
   end
@@ -29,7 +29,7 @@ end)
 
 describe("Output testing using string comparison with the has_error assertion", function()
   local getoutput = function(...)
-    local success, message = pcall(assert.has_error, ...)
+    local _, message = pcall(assert.has_error, ...)
     if message == nil then return nil end
     return tostring(message)
   end
@@ -105,7 +105,7 @@ end)
 
 describe("Output testing using string comparison with the same assertion", function()
   local getoutput = function(...)
-    local success, message = pcall(assert.are.same, ...)
+    local _, message = pcall(assert.are.same, ...)
     if message == nil then return nil end
     return tostring(message)
   end
@@ -138,14 +138,14 @@ describe("Output testing using custom failure message", function()
     local argcnt = select("#", ...)
     local args = {...}
     args[argcnt+1] = key .. " fails"
-    local success, message = pcall(assert[key], unpack(args, 1, argcnt+1))
+    local _, message = pcall(assert[key], unpack(args, 1, argcnt+1))
     if message == nil then return nil end
     message = tostring(message):gsub("\n.*", ""):gsub("^.-:%d+: ", "", 1)
     return message
   end
 
   local geterror2 = function(key, ...)
-    local success, message = pcall(assert.message(key .. " fails")[key], ...)
+    local _, message = pcall(assert.message(key .. " fails")[key], ...)
     if message == nil then return nil end
     message = tostring(message):gsub("\n.*", ""):gsub("^.-:%d+: ", "", 1)
     return message
@@ -329,7 +329,7 @@ for _,ss in ipairs({"spy", "stub"}) do
 
     local geterror = function(key, args, ...)
       local err = select('#', ...) == 0 and key .. " failed" or ...
-      local success, message = pcall(assert[ss](test.key, err)[key], unpack(args))
+      local _, message = pcall(assert[ss](test.key, err)[key], unpack(args))
       if message == nil then return nil end
       message = tostring(message):gsub("\n.*", ""):gsub("^.-:%d+: ", "", 1)
       return message
@@ -337,7 +337,7 @@ for _,ss in ipairs({"spy", "stub"}) do
 
     local geterror2 = function(key, args, ...)
       local err = select('#', ...) == 0 and key .. " failed" or ...
-      local success, message = pcall(assert.message(err).spy(test.key)[key], unpack(args))
+      local _, message = pcall(assert.message(err).spy(test.key)[key], unpack(args))
       if message == nil then return nil end
       message = tostring(message):gsub("\n.*", ""):gsub("^.-:%d+: ", "", 1)
       return message
diff --git a/spec/spies_spec.lua b/spec/spies_spec.lua
index e541336..ba058e5 100644
--- a/spec/spies_spec.lua
+++ b/spec/spies_spec.lua
@@ -4,6 +4,7 @@ describe("Tests dealing with spies", function()
   local test = {}
 
   before_each(function()
+    assert:set_parameter("TableFormatLevel", 3)
     test = {key = function()
       return "derp"
     end}
@@ -54,7 +55,16 @@ describe("Tests dealing with spies", function()
     assert.spy(s).was_not.returned_with(_, _, _, _) -- does not match if too many args
     assert.spy(s).was.returned_with({ foo = { bar = { "test" } } }) -- matches original table
     assert.spy(s).was_not.returned_with(t) -- does not match modified table
-    assert.has_error(function() assert.spy(s).was.returned_with(5, 6) end)
+    assert.error_matches(
+      function() assert.spy(s).returned_with(5, 6) end,
+      "Function never returned matching arguments.\n"
+      .. "Returned %(last call if any%):\n"
+      .. "%(values list%) %(%(table: 0x%x+%) {\n"
+      .. "  %[foo%] = {\n"
+      .. "    %[bar%] = {\n"
+      .. "      %[1%] = 'test' } } }.\n"
+      .. "Expected:\n"
+      .. "%(values list%) %(%(number%) 5, %(number%) 6%)")
   end)
 
   it("checks called() and called_with() assertions", function()
@@ -77,7 +87,16 @@ describe("Tests dealing with spies", function()
     assert.spy(s).was_not.called_with(_, _, _, _) -- does not match if too many args
     assert.spy(s).was.called_with({ foo = { bar = { "test" } } }) -- matches original table
     assert.spy(s).was_not.called_with(t) -- does not match modified table
-    assert.has_error(function() assert.spy(s).was.called_with(5, 6) end)
+    assert.error_matches(
+      function() assert.spy(s).was.called_with(5, 6) end,
+      "Function was never called with matching arguments.\n"
+      .. "Called with %(last call if any%):\n"
+      .. "%(values list%) %(%(table: 0x%x+%) {\n"
+      .. "  %[foo%] = {\n"
+      .. "    %[bar%] = {\n"
+      .. "      %[1%] = 'test' } } }%)\n"
+      .. "Expected:\n"
+      .. "%(values list%) %(%(number%) 5, %(number%) 6%)")
   end)
 
   it("checks called() and called_with() assertions using refs", function()
@@ -109,7 +128,9 @@ describe("Tests dealing with spies", function()
     assert.spy(s).was.called.at_least(1)
     assert.spy(s).was.called.at_least(2)
     assert.spy(s).was_not.called.at_least(3)
-    assert.has_error(function() assert.spy(s).was.called.at_least() end)
+    assert.error_matches(
+      function() assert.spy(s).was.called.at_least() end,
+      "attempt to compare nil with number")
   end)
 
   it("checks called_at_most() assertions", function()
@@ -120,7 +141,9 @@ describe("Tests dealing with spies", function()
     assert.spy(s).was.called.at_most(3)
     assert.spy(s).was.called.at_most(2)
     assert.spy(s).was_not.called.at_most(1)
-    assert.has_error(function() assert.spy(s).was.called.at_most() end)
+    assert.error_matches(
+      function() assert.spy(s).was.called.at_most() end,
+      "attempt to compare number with nil")
   end)
 
   it("checks called_more_than() assertions", function()
@@ -131,7 +154,9 @@ describe("Tests dealing with spies", function()
     assert.spy(s).was.called.more_than(0)
     assert.spy(s).was.called.more_than(1)
     assert.spy(s).was_not.called.more_than(2)
-    assert.has_error(function() assert.spy(s).was.called.more_than() end)
+    assert.error_matches(
+      function() assert.spy(s).was.called.more_than() end,
+      "attempt to compare nil with number")
   end)
 
   it("checks called_less_than() assertions", function()
@@ -142,10 +167,12 @@ describe("Tests dealing with spies", function()
     assert.spy(s).was.called.less_than(4)
     assert.spy(s).was.called.less_than(3)
     assert.spy(s).was_not.called.less_than(2)
-    assert.has_error(function() assert.spy(s).was.called.less_than() end)
+    assert.error_matches(
+      function() assert.spy(s).was.called.less_than() end,
+      "attempt to compare number with nil")
   end)
 
-  it("checkis if called()/called_with assertions fail on non-spies ", function()
+  it("checks if called()/called_with assertions fail on non-spies ", function()
     assert.has_error(assert.was.called)
     assert.has_error(assert.was.called_at_least)
     assert.has_error(assert.was.called_at_most)
@@ -223,4 +250,87 @@ describe("Tests dealing with spies", function()
     assert.spy(s).was.called(1)
   end)
 
+  it("reports some argumentslist the spy was called_with when none matches", function()
+    local s = spy.new(function() end)
+    s("herp", nil, "bust", nil)
+    assert.error_matches(
+      function() assert.spy(s).was.called_with() end,
+      "Function was never called with matching arguments.\n"
+      .. "Called with (last call if any):\n"
+      .. "(values list) ((string) 'herp', (nil), (string) 'bust', (nil))\n"
+      .. "Expected:\n"
+      .. "(values list) ()",
+      1, true)
+  end)
+
+  it("reports some matching call argumentslist when none should match", function()
+    assert:set_parameter("TableFormatLevel", 4)
+    local s = spy.new(function() end)
+    s({}, nil, {}, nil)
+    s("herp", nil, "bust", nil)
+    s({}, nil, {}, nil)
+    assert.error_matches(
+      function()
+        assert.spy(s).was_not.called_with(match.match("er"), nil, match.string(), nil)
+      end,
+      "Function was called with matching arguments at least once.\n"
+      .. "Called with (last matching call):\n"
+      .. "(values list) ((string) 'herp', (nil), (string) 'bust', (nil))\n"
+      .. "Did not expect:\n"
+      .. "(values list) ((matcher) is.match((string) 'er'), (nil), (matcher) is.string(), (nil))",
+      1, true)
+  end)
+
+  it("makes legible errors when never called", function()
+    local s = spy.new(function() end)
+    assert.error_matches(
+      function() assert.spy(s).was.called_with("derp", nil, "bust", nil) end,
+      "Function was never called with matching arguments.\n"
+      .. "Called with (last call if any):\n"
+      .. "(nil)\n"
+      .. "Expected:\n"
+      .. "(values list) ((string) 'derp', (nil), (string) 'bust', (nil))",
+      1, true)
+  end)
+
+  it("reports some return values from the spy when none mathes", function()
+    local s = spy.new(function(...) return ... end)
+    s("herp", nil, "bust", nil)
+    assert.error_matches(
+      function() assert.spy(s).returned_with("derp", nil, "bust", nil) end,
+      "Function never returned matching arguments.\n"
+      .. "Returned (last call if any):\n"
+      .. "(values list) ((string) 'herp', (nil), (string) 'bust', (nil))\n"
+      .. "Expected:\n"
+      .. "(values list) ((string) 'derp', (nil), (string) 'bust', (nil))",
+      1, true)
+  end)
+
+  it("reports some matching return values when none should match", function()
+    assert:set_parameter("TableFormatLevel", 4)
+    local s = spy.new(function(...) return ... end)
+    s({}, nil, {}, nil)
+    s("herp", nil, "bust", nil)
+    s({}, nil, {}, nil)
+    assert.error_matches(
+      function()
+        assert.spy(s).has_not.returned_with(match.matches("er"), nil, match.is_string(), nil)
+      end,
+      "Function returned matching arguments at least once.\n"
+      .. "Returned (last matching call):\n"
+      .. "(values list) ((string) 'herp', (nil), (string) 'bust', (nil))",
+      1, true)
+  end)
+
+  it("makes legible errors when never returned", function()
+    local s = spy.new(function(...) return ... end)
+    assert.error_matches(
+      function() assert.spy(s).returned_with() end,
+      "Function never returned matching arguments.\n"
+      .. "Returned (last call if any):\n"
+      .. "(nil)\n"
+      .. "Expected:\n"
+      .. "(values list) ()",
+      1, true)
+  end)
 end)
diff --git a/spec/state_spec.lua b/spec/state_spec.lua
index 8c2912f..0f5d51b 100644
--- a/spec/state_spec.lua
+++ b/spec/state_spec.lua
@@ -1,31 +1,31 @@
 describe("Tests states of the assert engine", function()
-    
+
   it("checks levels created/reverted", function()
     local start = assert:snapshot()
     assert.is_nil(start.next)
-    
+
     local snapshot1 = assert:snapshot()
     assert.is.table(start.next)
     assert.are.equal(start.next, snapshot1)
     assert.are.equal(start, snapshot1.previous)
     assert.is_nil(snapshot1.next)
-    
+
     local snapshot2 = assert:snapshot()
     assert.is.table(snapshot1.next)
     assert.are.equal(snapshot2, snapshot1.next)
     assert.are.equal(snapshot2.previous, snapshot1)
     assert.is_nil(snapshot2.next)
-    
+
     snapshot2:revert()
     assert.is.table(start.next)
     assert.are.equal(start.next, snapshot1)
     assert.are.equal(start, snapshot1.previous)
     assert.is_nil(snapshot1.next)
-    
+
     snapshot1:revert()
     assert.is_nil(start.next)
   end)
-  
+
   it("checks to see if a formatter is reversed", function()
 
     -- add a state level by creating a snapshot
@@ -45,7 +45,7 @@ describe("Tests states of the assert engine", function()
     assert.are.equal(snapshot1.formatters[1], fmt2)
     assert.are.equal("ok", assert:format({"some value"})[1])
     assert.are.equal("1", assert:format({123})[1])
-    
+
     -- add another state level by creating a snapshot
     local snapshot2 = assert:snapshot()
     -- register extra formatter
@@ -61,7 +61,7 @@ describe("Tests states of the assert engine", function()
     assert.are.equal("ok", assert:format({"some value"})[1])
     -- check formatter initial level
     assert.are.equal("(boolean) true", assert:format({true})[1])
-    
+
     -- revert 1 state up
     snapshot2:revert()
     assert.is_nil(snapshot1.next)
@@ -72,12 +72,12 @@ describe("Tests states of the assert engine", function()
     assert.are.equal("ok", assert:format({"some value"})[1])
     -- check formatter unchanged level
     assert.are.equal("(boolean) true", assert:format({true})[1])
-    
+
     -- revert 1 more up, to initial level
     snapshot1:revert()
     assert.are.equal("(number) 123", assert:format({123})[1])
     assert.are.equal("(string) 'some value'", assert:format({"some value"})[1])
-    assert.are.equal("(boolean) true", assert:format({true})[1])  
+    assert.are.equal("(boolean) true", assert:format({true})[1])
   end)
 
   it("checks to see if a parameter is reversed", function()
@@ -90,7 +90,7 @@ describe("Tests states of the assert engine", function()
     assert:set_parameter("Test_2", 2)
     assert.are.equal(1, assert:get_parameter("Test_1"))
     assert.are.equal(2, assert:get_parameter("Test_2"))
-    
+
     -- add another state level by creating a snapshot
     local snapshot2 = assert:snapshot()
     assert.are.equal(1, assert:get_parameter("Test_1"))
@@ -99,12 +99,12 @@ describe("Tests states of the assert engine", function()
     assert:set_parameter("Test_2", nil)    -- test setting to nil
     assert.are.equal("one", assert:get_parameter("Test_1"))
     assert.is_nil(assert:get_parameter("Test_2"))
-    
+
     -- revert 1 state up
     snapshot2:revert()
     assert.are.equal(1, assert:get_parameter("Test_1"))
     assert.are.equal(2, assert:get_parameter("Test_2"))
-    
+
     -- revert 1 more up, to initial level
     snapshot1:revert()
     assert.is_nil(assert:get_parameter("Test_1"))
@@ -118,8 +118,6 @@ describe("Tests states of the assert engine", function()
       f1 = function() c1 = c1 + 1 end,
       f2 = function() c2 = c2 + 1 end,
     }
-    local f1 = test.f1
-    local f2 = test.f2
     -- add a state level by creating a snapshot
     local snapshot1 = assert:snapshot()
     -- create spy/stub
@@ -132,7 +130,7 @@ describe("Tests states of the assert engine", function()
     assert.spy(test.f2).was.called(1)
     assert.is_equal(1, c1)
     assert.is_equal(0, c2) -- 0, because it's a stub
-    
+
     -- revert to initial level
     snapshot1:revert()
     test.f1()
@@ -141,7 +139,7 @@ describe("Tests states of the assert engine", function()
     assert.spy(s1).was.called(1)
     assert.spy(s2).was.called(1)
     assert.is_equal(2, c1)
-    assert.is_equal(1, c2) 
+    assert.is_equal(1, c2)
   end)
 
 end)
diff --git a/spec/stub_spec.lua b/spec/stub_spec.lua
index ceee4b1..5768de8 100644
--- a/spec/stub_spec.lua
+++ b/spec/stub_spec.lua
@@ -8,12 +8,19 @@ describe("Tests dealing with stubs", function()
       return "derp"
     end}
   end)
-  
+
   it("checks to see if stub keeps track of arguments", function()
     stub(test, "key")
     test.key("derp")
     assert.stub(test.key).was.called_with("derp")
-    assert.errors(function() assert.stub(test.key).was.called_with("herp") end)
+    assert.error_matches(
+      function() assert.stub(test.key).was.called_with("herp") end,
+      "Function was never called with matching arguments.\n"
+      .. "Called with (last call if any):\n"
+        .. "(values list) ((string) 'derp')\n"
+        .. "Expected:\n"
+        .. "(values list) ((string) 'herp')",
+      1, true)
   end)
 
   it("checks to see if stub keeps track of number of calls", function()
@@ -33,7 +40,14 @@ describe("Tests dealing with stubs", function()
     assert.stub(s).was_not.called(3)
     assert.stub(s).was_not.called_with({1, 2, 3}) -- mind the accolades
     assert.stub(s).was.called_with(1, 2, 3)
-    assert.has_error(function() assert.stub(s).was.called_with(5, 6) end)
+    assert.error_matches(
+      function() assert.stub(s).was.called_with(5, 6) end,
+      "Function was never called with matching arguments.\n"
+      .. "Called with (last call if any):\n"
+        .. "(values list) ((string) 'a', (string) 'b', (string) 'c')\n"
+        .. "Expected:\n"
+        .. "(values list) ((number) 5, (number) 6)",
+      1, true)
   end)
 
   it("checks stub to fail when spying on non-callable elements", function()
@@ -59,7 +73,7 @@ describe("Tests dealing with stubs", function()
      assert.is_table(s)
      s()
      s()
-     assert.stub(s).was.called(2)  
+     assert.stub(s).was.called(2)
      assert.are.equal(calls, 0)   -- its a stub, so no calls
      local old_s = s
      s = s:revert()
@@ -75,8 +89,7 @@ describe("Tests dealing with stubs", function()
      assert.is_table(s)
      s()
      s()
-     assert.stub(s).was.called(2)  
-     local old_s = s
+     assert.stub(s).was.called(2)
      s = s:revert()
      assert.is_nil(s)
   end)
@@ -86,8 +99,7 @@ describe("Tests dealing with stubs", function()
      assert.is_table(s)
      s()
      s()
-     assert.stub(s).was.called(2)  
-     local old_s = s
+     assert.stub(s).was.called(2)
      s = s:revert()
      assert.is_nil(s)
   end)
diff --git a/src/assert.lua b/src/assert.lua
index 27f9f0b..e0e2c89 100644
--- a/src/assert.lua
+++ b/src/assert.lua
@@ -1,7 +1,7 @@
 local s = require 'say'
 local astate = require 'luassert.state'
 local util = require 'luassert.util'
-local unpack = require 'luassert.compatibility'.unpack
+local unpack = util.unpack
 local obj   -- the returned module table
 local level_mt = {}
 
@@ -39,8 +39,7 @@ local __state_meta = {
         end
       end
 
-      local arguments = {...}
-      arguments.n = select('#', ...) -- add argument count for trailing nils
+      local arguments = util.make_arglist(...)
       local val, retargs = assertion.callback(self, arguments, util.errorlevel())
 
       if not val == self.mod then
@@ -57,8 +56,7 @@ local __state_meta = {
       end
       return ...
     else
-      local arguments = {...}
-      arguments.n = select('#', ...)
+      local arguments = util.make_arglist(...)
       self.tokens = {}
 
       for _, key in ipairs(keys) do
@@ -135,25 +133,25 @@ obj = {
   set_parameter = function(self, name, value)
     astate.set_parameter(name, value)
   end,
-  
+
   get_parameter = function(self, name)
     return astate.get_parameter(name)
-  end,  
-  
+  end,
+
   add_spy = function(self, spy)
     astate.add_spy(spy)
   end,
-  
+
   snapshot = function(self)
     return astate.snapshot()
   end,
-  
+
   level = function(self, level)
     return setmetatable({
         level = level
       }, level_mt)
   end,
-  
+
   -- returns the level if a level-value, otherwise nil
   get_level = function(self, level)
     if getmetatable(level) ~= level_mt then
diff --git a/src/assertions.lua b/src/assertions.lua
index 05970e7..19d056a 100644
--- a/src/assertions.lua
+++ b/src/assertions.lua
@@ -142,11 +142,15 @@ local function same(state, arguments, level)
 end
 
 local function truthy(state, arguments, level)
+  local argcnt = arguments.n
+  assert(argcnt > 0, s("assertion.internal.argtolittle", { "truthy", 1, tostring(argcnt) }), level)
   set_failure_message(state, arguments[2])
   return arguments[1] ~= false and arguments[1] ~= nil
 end
 
 local function falsy(state, arguments, level)
+  local argcnt = arguments.n
+  assert(argcnt > 0, s("assertion.internal.argtolittle", { "falsy", 1, tostring(argcnt) }), level)
   return not truthy(state, arguments, level)
 end
 
diff --git a/src/compatibility.lua b/src/compatibility.lua
index cf14f7d..88290ad 100644
--- a/src/compatibility.lua
+++ b/src/compatibility.lua
@@ -1,3 +1,9 @@
+-- no longer needed, only for backward compatibility
+local unpack = require ("luassert.util").unpack
+
 return {
-  unpack = table.unpack or unpack,
+  unpack = function(...)
+    print(debug.traceback("WARN: calling deprecated function 'luassert.compatibility.unpack' use 'luassert.util.unpack' instead"))
+    return unpack(...)
+  end
 }
diff --git a/src/formatters/init.lua b/src/formatters/init.lua
index a92ad29..3071e11 100644
--- a/src/formatters/init.lua
+++ b/src/formatters/init.lua
@@ -1,5 +1,7 @@
 -- module will not return anything, only register formatters with the main assert engine
 local assert = require('luassert.assert')
+local match = require('luassert.match')
+local util = require('luassert.util')
 
 local colors = setmetatable({
   none = function(c) return c end
@@ -201,6 +203,35 @@ local function fmt_thread(arg)
   end
 end
 
+local function fmt_matcher(arg)
+  if not match.is_matcher(arg) then
+    return
+  end
+  local not_inverted = {
+    [true] = "is.",
+    [false] = "no.",
+  }
+  local args = {}
+  for idx = 1, arg.arguments.n do
+    table.insert(args, assert:format({ arg.arguments[idx], n = 1, })[1])
+  end
+  return string.format("(matcher) %s%s(%s)",
+                       not_inverted[arg.mod],
+                       tostring(arg.name),
+                       table.concat(args, ", "))
+end
+
+local function fmt_arglist(arglist)
+  if not util.is_arglist(arglist) then
+    return
+  end
+  local formatted_vals = {}
+  for idx = 1, arglist.n do
+    table.insert(formatted_vals, assert:format({ arglist[idx], n = 1, })[1])
+  end
+  return "(values list) (" .. table.concat(formatted_vals, ", ") .. ")"
+end
+
 assert:add_formatter(fmt_string)
 assert:add_formatter(fmt_number)
 assert:add_formatter(fmt_boolean)
@@ -209,6 +240,8 @@ assert:add_formatter(fmt_table)
 assert:add_formatter(fmt_function)
 assert:add_formatter(fmt_userdata)
 assert:add_formatter(fmt_thread)
+assert:add_formatter(fmt_matcher)
+assert:add_formatter(fmt_arglist)
 -- Set default table display depth for table formatter
 assert:set_parameter("TableFormatLevel", 3)
 assert:set_parameter("TableFormatShowRecursion", false)
diff --git a/src/languages/en.lua b/src/languages/en.lua
index c8bcc38..0d84b6d 100644
--- a/src/languages/en.lua
+++ b/src/languages/en.lua
@@ -34,11 +34,11 @@ s:set("assertion.called_at_most.positive", "Expected to be called at most %s tim
 s:set("assertion.called_more_than.positive", "Expected to be called more than %s time(s), but was called %s time(s)")
 s:set("assertion.called_less_than.positive", "Expected to be called less than %s time(s), but was called %s time(s)")
 
-s:set("assertion.called_with.positive", "Function was not called with the arguments")
-s:set("assertion.called_with.negative", "Function was called with the arguments")
+s:set("assertion.called_with.positive", "Function was never called with matching arguments.\nCalled with (last call if any):\n%s\nExpected:\n%s")
+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")
 
-s:set("assertion.returned_with.positive", "Function was not returned with the arguments")
-s:set("assertion.returned_with.negative", "Function was returned with the arguments")
+s:set("assertion.returned_with.positive", "Function never returned matching arguments.\nReturned (last call if any):\n%s\nExpected:\n%s")
+s:set("assertion.returned_with.negative", "Function returned matching arguments at least once.\nReturned (last matching call):\n%s\nDid not expect:\n%s")
 
 s:set("assertion.returned_arguments.positive", "Expected to be called with %s argument(s), but was called with %s")
 s:set("assertion.returned_arguments.negative", "Expected not to be called with %s argument(s), but was called with %s")
diff --git a/src/match.lua b/src/match.lua
index 12901f4..671c82f 100644
--- a/src/match.lua
+++ b/src/match.lua
@@ -25,17 +25,16 @@ local state_mt = {
         end
       end
 
-      local arguments = {...}
-      arguments.n = select('#', ...) -- add argument count for trailing nils
+      local arguments = util.make_arglist(...)
       local matches = matcher.callback(self, arguments, util.errorlevel())
       return setmetatable({
         name = matcher.name,
         mod = self.mod,
         callback = matches,
+        arguments = arguments,
       }, matcher_mt)
     else
-      local arguments = {...}
-      arguments.n = select('#', ...) -- add argument count for trailing nils
+      local arguments = util.make_arglist(...)
 
       for _, key in ipairs(keys) do
         if namespace.modifier[key] then
diff --git a/src/matchers/core.lua b/src/matchers/core.lua
index e6b4ca1..4335baf 100644
--- a/src/matchers/core.lua
+++ b/src/matchers/core.lua
@@ -60,7 +60,6 @@ local function matches(state, arguments, level)
   local pattern = arguments[1]
   local init = arguments[2]
   local plain = arguments[3]
-  local stringtype = "string or object convertible to a string"
   assert(type(pattern) == "string", s("assertion.internal.badargtype", { 1, "matches", "string", type(arguments[1]) }), level)
   assert(init == nil or tonumber(init), s("assertion.internal.badargtype", { 2, "matches", "number", type(arguments[2]) }), level)
 
diff --git a/src/spy.lua b/src/spy.lua
index c57cc7b..eb7fc06 100644
--- a/src/spy.lua
+++ b/src/spy.lua
@@ -5,12 +5,10 @@ local util = require('luassert.util')
 -- Spy metatable
 local spy_mt = {
   __call = function(self, ...)
-    local arguments = {...}
-    arguments.n = select('#',...)  -- add argument count for trailing nils
+    local arguments = util.make_arglist(...)
     table.insert(self.calls, util.copyargs(arguments))
     local function get_returns(...)
-      local returnvals = {...}
-      returnvals.n = select('#',...)  -- add argument count for trailing nils
+      local returnvals = util.make_arglist(...)
       table.insert(self.returnvals, util.copyargs(returnvals))
       return ...
     end
@@ -59,11 +57,27 @@ spy = {
       end,
 
       called_with = function(self, args)
-        return util.matchargs(self.calls, args) ~= nil
+        local last_arglist = nil
+        if #self.calls > 0 then
+          last_arglist = self.calls[#self.calls].vals
+        end
+        local matching_arglists = util.matchargs(self.calls, args)
+        if matching_arglists ~= nil then
+          return true, matching_arglists.vals
+        end
+        return false, last_arglist
       end,
 
       returned_with = function(self, args)
-        return util.matchargs(self.returnvals, args) ~= nil
+        local last_returnvallist = nil
+        if #self.returnvals > 0 then
+          last_returnvallist = self.returnvals[#self.returnvals].vals
+        end
+        local matching_returnvallists = util.matchargs(self.returnvals, args)
+        if matching_returnvallists ~= nil then
+          return true, matching_returnvallists.vals
+        end
+        return false, last_returnvallist
       end
     }, spy_mt)
     assert:add_spy(s)  -- register with the current state
@@ -96,7 +110,12 @@ local function returned_with(state, arguments, level)
   local level = (level or 1) + 1
   local payload = rawget(state, "payload")
   if payload and payload.returned_with then
-    return state.payload:returned_with(arguments)
+    local assertion_holds, matching_or_last_returnvallist = state.payload:returned_with(arguments)
+    local expected_returnvallist = util.shallowcopy(arguments)
+    util.cleararglist(arguments)
+    util.tinsert(arguments, 1, matching_or_last_returnvallist)
+    util.tinsert(arguments, 2, expected_returnvallist)
+    return assertion_holds
   else
     error("'returned_with' must be chained after 'spy(aspy)'", level)
   end
@@ -106,7 +125,12 @@ local function called_with(state, arguments, level)
   local level = (level or 1) + 1
   local payload = rawget(state, "payload")
   if payload and payload.called_with then
-    return state.payload:called_with(arguments)
+    local assertion_holds, matching_or_last_arglist = state.payload:called_with(arguments)
+    local expected_arglist = util.shallowcopy(arguments)
+    util.cleararglist(arguments)
+    util.tinsert(arguments, 1, matching_or_last_arglist)
+    util.tinsert(arguments, 2, expected_arglist)
+    return assertion_holds
   else
     error("'called_with' must be chained after 'spy(aspy)'", level)
   end
diff --git a/src/state.lua b/src/state.lua
index 7e64483..6de0efe 100644
--- a/src/state.lua
+++ b/src/state.lua
@@ -30,7 +30,7 @@ state.revert = function(self)
     end
   end
   if getmetatable(self) ~= state_mt then error("Value provided is not a valid snapshot", 2) end
-  
+
   if self.next then
     self.next:revert()
   end
@@ -52,7 +52,6 @@ end
 -- Creates a new snapshot.
 -- @return snapshot table
 state.snapshot = function()
-  local s = current
   local new = setmetatable ({
     formatters = {},
     parameters = {},
diff --git a/src/stub.lua b/src/stub.lua
index 969c859..91ae6e0 100644
--- a/src/stub.lua
+++ b/src/stub.lua
@@ -2,7 +2,8 @@
 local assert = require 'luassert.assert'
 local spy = require 'luassert.spy'
 local util = require 'luassert.util'
-local unpack = require 'luassert.compatibility'.unpack
+local unpack = util.unpack
+local pack = util.pack
 
 local stub = {}
 
@@ -12,22 +13,20 @@ function stub.new(object, key, ...)
     object = {}
     key = ""
   end
-  local return_values_count = select("#", ...)
-  local return_values = {...}
+  local return_values = pack(...)
   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())
   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())
   local old_elem = object[key]    -- keep existing element (might be nil!)
 
-  local fn = (return_values_count == 1 and util.callable(return_values[1]) and return_values[1])
+  local fn = (return_values.n == 1 and util.callable(return_values[1]) and return_values[1])
   local defaultfunc = fn or function()
-    return unpack(return_values, 1, return_values_count)
+    return unpack(return_values)
   end
   local oncalls = {}
   local callbacks = {}
   local stubfunc = function(...)
-    local args = {...}
-    args.n = select('#', ...)
-    local match = util.matchargs(oncalls, args)
+    local args = util.make_arglist(...)
+    local match = util.matchoncalls(oncalls, args)
     if match then
       return callbacks[match](...)
     end
@@ -48,10 +47,9 @@ function stub.new(object, key, ...)
   end
 
   s.returns = function(...)
-    local return_args = {...}
-    local n = select('#', ...)
+    local return_args = pack(...)
     defaultfunc = function()
-      return unpack(return_args, 1, n)
+      return unpack(return_args)
     end
     return s
   end
@@ -69,16 +67,14 @@ function stub.new(object, key, ...)
   }
 
   s.on_call_with = function(...)
-    local match_args = {...}
-    match_args.n = select('#', ...)
+    local match_args = util.make_arglist(...)
     match_args = util.copyargs(match_args)
     return {
       returns = function(...)
-        local return_args = {...}
-        local n = select('#', ...)
+        local return_args = pack(...)
         table.insert(oncalls, match_args)
         callbacks[match_args] = function()
-          return unpack(return_args, 1, n)
+          return unpack(return_args)
         end
         return s
       end,
diff --git a/src/util.lua b/src/util.lua
index ba90b4b..e2e9a27 100644
--- a/src/util.lua
+++ b/src/util.lua
@@ -1,4 +1,14 @@
 local util = {}
+local arglist_mt = {}
+
+-- have pack/unpack both respect the 'n' field
+local _unpack = table.unpack or unpack
+local unpack = function(t, i, j) return _unpack(t, i or 1, j or t.n or #t) end
+local pack = function(...) return { n = select("#", ...), ... } end
+util.pack = pack
+util.unpack = unpack
+
+
 function util.deepcompare(t1,t2,ignore_mt,cycles,thresh1,thresh2)
   local ty1 = type(t1)
   local ty2 = type(t2)
@@ -58,6 +68,7 @@ end
 function util.shallowcopy(t)
   if type(t) ~= "table" then return t end
   local copy = {}
+  setmetatable(copy, getmetatable(t))
   for k,v in next, t do
     copy[k] = v
   end
@@ -91,6 +102,7 @@ end
 -- @return the copy of the arguments
 function util.copyargs(args)
   local copy = {}
+  setmetatable(copy, getmetatable(args))
   local match = require 'luassert.match'
   local spy = require 'luassert.spy'
   for k,v in pairs(args) do
@@ -100,42 +112,90 @@ function util.copyargs(args)
 end
 
 -----------------------------------------------
--- Finds matching arguments in a saved list of arguments
--- @param argslist list of arguments from which to search
--- @param args the arguments of which to find a match
--- @return the matching arguments if a match is found, otherwise nil
-function util.matchargs(argslist, args)
-  local function matches(t1, t2, t1refs)
-    local match = require 'luassert.match'
-    for k1,v1 in pairs(t1) do
-      local v2 = t2[k1]
-      if match.is_matcher(v1) then
-        if not v1(v2) then return false end
-      elseif match.is_matcher(v2) then
-        if match.is_ref_matcher(v2) then v1 = t1refs[k1] end
-        if not v2(v1) then return false end
-      elseif (v2 == nil or not util.deepcompare(v1,v2)) then
+-- Clear an arguments or return values list from a table
+-- @param arglist the table to clear of arguments or return values and their count
+-- @return No return values
+function util.cleararglist(arglist)
+  for idx = arglist.n, 1, -1 do
+    util.tremove(arglist, idx)
+  end
+  arglist.n = nil
+end
+
+-----------------------------------------------
+-- Test specs against an arglist in deepcopy and refs flavours.
+-- @param args deepcopy arglist
+-- @param argsrefs refs arglist
+-- @param specs arguments/return values to match against args/argsrefs
+-- @return true if specs match args/argsrefs, false otherwise
+local function matcharg(args, argrefs, specs)
+  local match = require 'luassert.match'
+  for idx, argval in pairs(args) do
+    local spec = specs[idx]
+    if match.is_matcher(spec) then
+      if match.is_ref_matcher(spec) then
+        argval = argrefs[idx]
+      end
+      if not spec(argval) then
         return false
       end
+    elseif (spec == nil or not util.deepcompare(argval, spec)) then
+      return false
     end
-    for k2,v2 in pairs(t2) do
-      -- only check wether each element has a t1 counterpart, actual comparison
-      -- has been done in first loop above
-      local v1 = t1[k2]
-      if v1 == nil then
-        -- no t1 counterpart, so try to compare using matcher
-        if match.is_matcher(v2) then
-          if not v2(v1) then return false end
-        else
+  end
+
+  for idx, spec in pairs(specs) do
+    -- only check whether each element has an args counterpart,
+    -- actual comparison has been done in first loop above
+    local argval = args[idx]
+    if argval == nil then
+      -- no args counterpart, so try to compare using matcher
+      if match.is_matcher(spec) then
+        if not spec(argval) then
           return false
         end
+      else
+        return false
       end
     end
-    return true
   end
-  for k,v in ipairs(argslist) do
-    if matches(v.vals, args, v.refs) then
-      return v
+  return true
+end
+
+-----------------------------------------------
+-- Find matching arguments/return values in a saved list of
+-- arguments/returned values.
+-- @param invocations_list list of arguments/returned values to search (list of lists)
+-- @param specs arguments/return values to match against argslist
+-- @return the last matching arguments/returned values if a match is found, otherwise nil
+function util.matchargs(invocations_list, specs)
+  -- Search the arguments/returned values last to first to give the
+  -- most helpful answer possible. In the cases where you can place
+  -- your assertions between calls to check this gives you the best
+  -- information if no calls match. In the cases where you can't do
+  -- that there is no good way to predict what would work best.
+  assert(not util.is_arglist(invocations_list), "expected a list of arglist-object, got an arglist")
+  for ii = #invocations_list, 1, -1 do
+    local val = invocations_list[ii]
+    if matcharg(val.vals, val.refs, specs) then
+      return val
+    end
+  end
+  return nil
+end
+
+-----------------------------------------------
+-- Find matching oncall for an actual call.
+-- @param oncalls list of oncalls to search
+-- @param args actual call argslist to match against
+-- @return the first matching oncall if a match is found, otherwise nil
+function util.matchoncalls(oncalls, args)
+  for _, callspecs in ipairs(oncalls) do
+    -- This lookup is done immediately on *args* passing into the stub
+    -- so pass *args* as both *args* and *argsref* without copying
+    -- either.
+    if matcharg(args, args, callspecs.vals) then
+      return callspecs
     end
   end
   return nil
@@ -283,4 +343,19 @@ function util.extract_keys(nspace, tokens)
   return keys
 end
 
+-----------------------------------------------
+-- store argument list for return values of a function in a table.
+-- The table will get a metatable to identify it as an arglist
+function util.make_arglist(...)
+  local arglist = { ... }
+  arglist.n = select('#', ...) -- add values count for trailing nils
+  return setmetatable(arglist, arglist_mt)
+end
+
+-----------------------------------------------
+-- check a table to be an arglist type.
+function util.is_arglist(object)
+  return getmetatable(object) == arglist_mt
+end
+
 return util