------------------------------------------------------------------------ --- Assertors local assertors = {} local function register_assertor (name, callback, error_message) assertors[name] = function (...) local bool = callback(...) if bool then return end local success, formatted_message = pcall(string.format, error_message, ...) if not success then error('assertion failed, and error message could not be formatted', 2) end error('\n' .. formatted_message or 'assertion failed!', 2) end end --- Value is truthy local function is_truthy (x) return x ~= false and x ~= nil end --- Value is falsy local function is_falsy (x) return not is_truthy(x) end --- Value is nil local function is_nil (x) return x == nil end --- Values are equal local function are_equal (x, y) return x == y end local function cycle_aware_compare(t1, t2, cache) if cache[t1] and cache[t1][t2] then return true end local ty1 = type(t1) local ty2 = type(t2) -- if t1 == t2 then return true end if ty1 ~= ty2 then return false end if ty1 ~= 'table' then return t1 == t2 end -- Check tables have the same set of keys for k1 in pairs(t1) do if t2[k1] == nil then return false end end for k2 in pairs(t2) do if t1[k2] == nil then return false end end -- cache comparison result cache[t1] = cache[t1] or {} cache[t1][t2] = true for k1, v1 in pairs(t1) do local v2 = t2[k1] if not cycle_aware_compare(v1, v2, cache) then return false end end return true end --- Check if tables are the same local function are_same(x, y) return cycle_aware_compare(x, y, {}) end local function error_matches(fn, pattern) local success, msg = pcall(fn) if success then return false end return msg:match(pattern) end register_assertor('is_truthy', is_truthy, "expected a truthy value, got %s") register_assertor('is_falsy', is_falsy, "expected a falsy value, got %s") register_assertor('is_nil', is_nil, "expected nil, got %s") register_assertor('are_same', are_same, 'expected same values, got %s and %s') register_assertor( 'are_equal', are_equal, "expected values to be equal, got '%s' and '%s'" ) register_assertor( 'error_matches', error_matches, 'no error matching the given pattern was raised' ) ------------------------------------------------------------------------ local ok = true local function test_success (name) return { name = name, result = ok, } end local function test_failure (name, err_msg) return { name = name, result = err_msg, } end ------------------------------------------------------------------------ -- Test definitions local function test_case (name, callback) local success, err_msg = pcall(callback) return success and test_success(name) or test_failure(name, err_msg) end local function test_group (name, tests) if tests == nil then -- let's try to curry return function (tests_) return tests_ and test_group(name, tests_) or error('no tests') end end return { name = name, result = tests, } end return { assert = assertors, ok = ok, test_case = test_case, test_group = test_group }