# HG changeset patch # User Michael Roth # Date 1189217971 14400 # Node ID 4f81725a1e5fd17ab74a042ba4de1c71b8f8a5e0 # Parent 64a79d8ee2240aa360a8b938a968cbe9ae351c4b Added lunit 0.4a from Michael Roth (author of Lua-sqlite3) Gleaned from lua-sqlite3 since the `public` version of lunit is 0.3a diff -r 64a79d8ee224 -r 4f81725a1e5f test/lunit.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/lunit.lua Fri Sep 07 22:19:31 2007 -0400 @@ -0,0 +1,693 @@ + +--[[-------------------------------------------------------------------------- + + This file is part of lunit 0.4pre (alpha). + + For Details about lunit look at: http://www.nessie.de/mroth/lunit/ + + Author: Michael Roth + + Copyright (c) 2004 Michael Roth + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--]]-------------------------------------------------------------------------- + + + + +----------------------- +-- Intialize package -- +----------------------- + +local P = { } +lunit = P + +-- Import +local type = type +local print = print +local ipairs = ipairs +local pairs = pairs +local string = string +local table = table +local pcall = pcall +local xpcall = xpcall +local traceback = debug.traceback +local error = error +local setmetatable = setmetatable +local rawset = rawset +local orig_assert = assert +local getfenv = getfenv +local setfenv = setfenv +local tostring = tostring + + +-- Start package scope +setfenv(1, P) + + + + +-------------------------------- +-- Private data and functions -- +-------------------------------- + +local run_testcase +local do_assert, check_msg +local stats = { } +local testcases = { } +local stats_inc, tc_mt + + + + +-------------------------- +-- Type check functions -- +-------------------------- + +function is_nil(x) + return type(x) == "nil" +end + +function is_boolean(x) + return type(x) == "boolean" +end + +function is_number(x) + return type(x) == "number" +end + +function is_string(x) + return type(x) == "string" +end + +function is_table(x) + return type(x) == "table" +end + +function is_function(x) + return type(x) == "function" +end + +function is_thread(x) + return type(x) == "thread" +end + +function is_userdata(x) + return type(x) == "userdata" +end + + + + +---------------------- +-- Assert functions -- +---------------------- + +function assert(assertion, msg) + stats_inc("assertions") + check_msg("assert", msg) + do_assert(not not assertion, "assertion failed (was: "..tostring(assertion)..")", msg) -- (convert assertion to bool) + return assertion +end + + +function assert_fail(msg) + stats_inc("assertions") + check_msg("assert_fail", msg) + do_assert(false, "failure", msg) +end + + +function assert_true(actual, msg) + stats_inc("assertions") + check_msg("assert_true", msg) + do_assert(is_boolean(actual), "true expected but was a "..type(actual), msg) + do_assert(actual == true, "true expected but was false", msg) + return actual +end + + +function assert_false(actual, msg) + stats_inc("assertions") + check_msg("assert_false", msg) + do_assert(is_boolean(actual), "false expected but was a "..type(actual), msg) + do_assert(actual == false, "false expected but was true", msg) + return actual +end + + +function assert_equal(expected, actual, msg) + stats_inc("assertions") + check_msg("assert_equal", msg) + do_assert(expected == actual, "expected '"..tostring(expected).."' but was '"..tostring(actual).."'", msg) + return actual +end + + +function assert_not_equal(unexpected, actual, msg) + stats_inc("assertions") + check_msg("assert_not_equal", msg) + do_assert(unexpected ~= actual, "'"..tostring(expected).."' not expected but was one", msg) + return actual +end + + +function assert_match(pattern, actual, msg) + stats_inc("assertions") + check_msg("assert_match", msg) + do_assert(is_string(pattern), "assert_match expects the pattern as a string") + do_assert(is_string(actual), "expected a string to match pattern '"..pattern.."' but was a '"..type(actual).."'", msg) + do_assert(not not string.find(actual, pattern), "expected '"..actual.."' to match pattern '"..pattern.."' but doesn't", msg) + return actual +end + + +function assert_not_match(pattern, actual, msg) + stats_inc("assertions") + check_msg("assert_not_match", msg) + do_assert(is_string(actual), "expected a string to not match pattern '"..pattern.."' but was a '"..type(actual).."'", msg) + do_assert(string.find(actual, pattern) == nil, "expected '"..actual.."' to not match pattern '"..pattern.."' but it does", msg) + return actual +end + + +function assert_nil(actual, msg) + stats_inc("assertions") + check_msg("assert_nil", msg) + do_assert(is_nil(actual), "nil expected but was a "..type(actual), msg) + return actual +end + + +function assert_not_nil(actual, msg) + stats_inc("assertions") + check_msg("assert_not_nil", msg) + do_assert(not is_nil(actual), "nil not expected but was one", msg) + return actual +end + + +function assert_boolean(actual, msg) + stats_inc("assertions") + check_msg("assert_boolean", msg) + do_assert(is_boolean(actual), "boolean expected but was a "..type(actual), msg) + return actual +end + + +function assert_not_boolean(actual, msg) + stats_inc("assertions") + check_msg("assert_not_boolean", msg) + do_assert(not is_boolean(actual), "boolean not expected but was one", msg) + return actual +end + + +function assert_number(actual, msg) + stats_inc("assertions") + check_msg("assert_number", msg) + do_assert(is_number(actual), "number expected but was a "..type(actual), msg) + return actual +end + + +function assert_not_number(actual, msg) + stats_inc("assertions") + check_msg("assert_not_number", msg) + do_assert(not is_number(actual), "number not expected but was one", msg) + return actual +end + + +function assert_string(actual, msg) + stats_inc("assertions") + check_msg("assert_string", msg) + do_assert(is_string(actual), "string expected but was a "..type(actual), msg) + return actual +end + + +function assert_not_string(actual, msg) + stats_inc("assertions") + check_msg("assert_not_string", msg) + do_assert(not is_string(actual), "string not expected but was one", msg) + return actual +end + + +function assert_table(actual, msg) + stats_inc("assertions") + check_msg("assert_table", msg) + do_assert(is_table(actual), "table expected but was a "..type(actual), msg) + return actual +end + + +function assert_not_table(actual, msg) + stats_inc("assertions") + check_msg("assert_not_table", msg) + do_assert(not is_table(actual), "table not expected but was one", msg) + return actual +end + + +function assert_function(actual, msg) + stats_inc("assertions") + check_msg("assert_function", msg) + do_assert(is_function(actual), "function expected but was a "..type(actual), msg) + return actual +end + + +function assert_not_function(actual, msg) + stats_inc("assertions") + check_msg("assert_not_function", msg) + do_assert(not is_function(actual), "function not expected but was one", msg) + return actual +end + + +function assert_thread(actual, msg) + stats_inc("assertions") + check_msg("assert_thread", msg) + do_assert(is_thread(actual), "thread expected but was a "..type(actual), msg) + return actual +end + + +function assert_not_thread(actual, msg) + stats_inc("assertions") + check_msg("assert_not_thread", msg) + do_assert(not is_thread(actual), "thread not expected but was one", msg) + return actual +end + + +function assert_userdata(actual, msg) + stats_inc("assertions") + check_msg("assert_userdata", msg) + do_assert(is_userdata(actual), "userdata expected but was a "..type(actual), msg) + return actual +end + + +function assert_not_userdata(actual, msg) + stats_inc("assertions") + check_msg("assert_not_userdata", msg) + do_assert(not is_userdata(actual), "userdata not expected but was one", msg) + return actual +end + + +function assert_error(msg, func) + stats_inc("assertions") + if is_nil(func) then func, msg = msg, nil end + check_msg("assert_error", msg) + do_assert(is_function(func), "assert_error expects a function as the last argument but it was a "..type(func)) + local ok, errmsg = pcall(func) + do_assert(ok == false, "error expected but no error occurred", msg) +end + + +function assert_pass(msg, func) + stats_inc("assertions") + if is_nil(func) then func, msg = msg, nil end + check_msg("assert_pass", msg) + do_assert(is_function(func), "assert_pass expects a function as the last argument but it was a "..type(func)) + local ok, errmsg = pcall(func) + if not ok then do_assert(ok == true, "no error expected but error was: "..errmsg, msg) end +end + + + + +----------------------------------------------------------- +-- Assert implementation that assumes it was called from -- +-- lunit code which was called directly from user code. -- +----------------------------------------------------------- + +function do_assert(assertion, base_msg, user_msg) + orig_assert(is_boolean(assertion)) + orig_assert(is_string(base_msg)) + orig_assert(is_string(user_msg) or is_nil(user_msg)) + if not assertion then + if user_msg then + error(base_msg..": "..user_msg, 3) + else + error(base_msg.."!", 3) + end + end +end + +------------------------------------------- +-- Checks the msg argument in assert_xxx -- +------------------------------------------- + +function check_msg(name, msg) + orig_assert(is_string(name)) + if not (is_nil(msg) or is_string(msg)) then + error("lunit."..name.."() expects the optional message as a string but it was a "..type(msg).."!" ,3) + end +end + + + + +------------------------------------- +-- Creates a new TestCase 'Object' -- +------------------------------------- + +function TestCase(name) + do_assert(is_string(name), "lunit.TestCase() needs a string as an argument") + local tc = { + __lunit_name = name; + __lunit_setup = nil; + __lunit_tests = { }; + __lunit_teardown = nil; + } + setmetatable(tc, tc_mt) + table.insert(testcases, tc) + return tc +end + +tc_mt = { + __newindex = function(tc, key, value) + rawset(tc, key, value) + if is_string(key) and is_function(value) then + local name = string.lower(key) + if string.find(name, "^test") or string.find(name, "test$") then + table.insert(tc.__lunit_tests, key) + elseif name == "setup" then + tc.__lunit_setup = value + elseif name == "teardown" then + tc.__lunit_teardown = value + end + end + end +} + + + +----------------------------------------- +-- Wrap Functions in a TestCase object -- +----------------------------------------- + +function wrap(name, ...) + if is_function(name) then + table.insert(arg, 1, name) + name = "Anonymous Testcase" + end + + local tc = TestCase(name) + for index, test in ipairs(arg) do + tc["Test #"..index] = test + end + return tc +end + + + + + + +---------------------------------- +-- Runs the complete Test Suite -- +---------------------------------- + +function run() + + --------------------------- + -- Initialize statistics -- + --------------------------- + + stats.testcases = 0 -- Total number of Test Cases + stats.tests = 0 -- Total number of all Tests in all Test Cases + stats.run = 0 -- Number of Tests run + stats.notrun = 0 -- Number of Tests not run + stats.failed = 0 -- Number of Tests failed + stats.warnings = 0 -- Number of Warnings (teardown) + stats.errors = 0 -- Number of Errors (setup) + stats.passed = 0 -- Number of Test passed + stats.assertions = 0 -- Number of all assertions made in all Test in all Test Cases + + -------------------------------- + -- Count Test Cases and Tests -- + -------------------------------- + + stats.testcases = table.getn(testcases) + + for _, tc in ipairs(testcases) do + stats_inc("tests" , table.getn(tc.__lunit_tests)) + end + + ------------------ + -- Print Header -- + ------------------ + + print() + print("#### Test Suite with "..stats.tests.." Tests in "..stats.testcases.." Test Cases loaded.") + + ------------------------ + -- Run all Test Cases -- + ------------------------ + + for _, tc in ipairs(testcases) do + run_testcase(tc) + end + + ------------------ + -- Print Footer -- + ------------------ + + print() + print("#### Test Suite finished.") + + local msg_assertions = stats.assertions.." Assertions checked. " + local msg_passed = stats.passed == stats.tests and "All Tests passed" or stats.passed.." Tests passed" + local msg_failed = stats.failed > 0 and ", "..stats.failed.." failed" or "" + local msg_run = stats.notrun > 0 and ", "..stats.notrun.." not run" or "" + local msg_warn = stats.warnings > 0 and ", "..stats.warnings.." warnings" or "" + + print() + print(msg_assertions..msg_passed..msg_failed..msg_run..msg_warn.."!") + + ----------------- + -- Return code -- + ----------------- + + if stats.passed == stats.tests then + return 0 + else + return 1 + end +end + + + + +----------------------------- +-- Runs a single Test Case -- +----------------------------- + +function run_testcase(tc) + + orig_assert(is_table(tc)) + orig_assert(is_table(tc.__lunit_tests)) + orig_assert(is_string(tc.__lunit_name)) + orig_assert(is_nil(tc.__lunit_setup) or is_function(tc.__lunit_setup)) + orig_assert(is_nil(tc.__lunit_teardown) or is_function(tc.__lunit_teardown)) + + ---------------------------------- + -- Protected call to a function -- + ---------------------------------- + + local function call(errprefix, func) + orig_assert(is_string(errprefix)) + orig_assert(is_function(func)) + local ok, errmsg = xpcall(function() func(tc) end, traceback) + if not ok then + print() + print(errprefix..": "..errmsg) + end + return ok + end + + ------------------------------------ + -- Calls setup() on the Test Case -- + ------------------------------------ + + local function setup(testname) + if tc.__lunit_setup then + return call("ERROR: "..testname..": setup() failed", tc.__lunit_setup) + else + return true + end + end + + ------------------------------------------ + -- Calls a single Test on the Test Case -- + ------------------------------------------ + + local function run(testname) + orig_assert(is_string(testname)) + orig_assert(is_function(tc[testname])) + local ok = call("FAIL: "..testname, tc[testname]) + if not ok then + stats_inc("failed") + else + stats_inc("passed") + end + return ok + end + + --------------------------------------- + -- Calls teardown() on the Test Case -- + --------------------------------------- + + local function teardown(testname) + if tc.__lunit_teardown then + if not call("WARNING: "..testname..": teardown() failed", tc.__lunit_teardown) then + stats_inc("warnings") + end + end + end + + --------------------------------- + -- Run all Tests on a TestCase -- + --------------------------------- + + print() + print("#### Running '"..tc.__lunit_name.."' ("..table.getn(tc.__lunit_tests).." Tests)...") + + for _, testname in ipairs(tc.__lunit_tests) do + if setup(testname) then + run(testname) + stats_inc("run") + teardown(testname) + else + print("WARN: Skipping '"..testname.."'...") + stats_inc("notrun") + end + end + +end + + + + +--------------------- +-- Import function -- +--------------------- + +function import(name) + + do_assert(is_string(name), "lunit.import() expects a single string as argument") + + local user_env = getfenv(2) + + -------------------------------------------------- + -- Installs a specific function in the user env -- + -------------------------------------------------- + + local function install(funcname) + user_env[funcname] = P[funcname] + end + + + ---------------------------------------------------------- + -- Install functions matching a pattern in the user env -- + ---------------------------------------------------------- + + local function install_pattern(pattern) + for funcname, _ in pairs(P) do + if string.find(funcname, pattern) then + install(funcname) + end + end + end + + ------------------------------------------------------------ + -- Installs assert() and all assert_xxx() in the user env -- + ------------------------------------------------------------ + + local function install_asserts() + install_pattern("^assert.*") + end + + ------------------------------------------- + -- Installs all is_xxx() in the user env -- + ------------------------------------------- + + local function install_tests() + install_pattern("^is_.+") + end + + if name == "asserts" or name == "assertions" then + install_asserts() + elseif name == "tests" or name == "checks" then + install_tests() + elseif name == "all" then + install_asserts() + install_tests() + install("TestCase") + elseif string.find(name, "^assert.*") and P[name] then + install(name) + elseif string.find(name, "^is_.+") and P[name] then + install(name) + elseif name == "TestCase" then + install("TestCase") + else + error("luniit.import(): invalid function '"..name.."' to import", 2) + end +end + + + + +-------------------------------------------------- +-- Installs a private environment on the caller -- +-------------------------------------------------- + +function setprivfenv() + local new_env = { } + local new_env_mt = { __index = getfenv(2) } + setmetatable(new_env, new_env_mt) + setfenv(2, new_env) +end + + + + +-------------------------------------------------- +-- Increments a counter in the statistics table -- +-------------------------------------------------- + +function stats_inc(varname, value) + orig_assert(is_table(stats)) + orig_assert(is_string(varname)) + orig_assert(is_nil(value) or is_number(value)) + if not stats[varname] then return end + stats[varname] = stats[varname] + (value or 1) +end + + + +