| |
| --[[-------------------------------------------------------------------------- |
| |
| This file is part of lunit 0.4pre (alpha). |
| |
| For Details about lunit look at: http://www.nessie.de/mroth/lunit/ |
| |
| Author: Michael Roth <mroth@nessie.de> |
| |
| Copyright (c) 2004 Michael Roth <mroth@nessie.de> |
| |
| 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({...}, 1, name) |
| name = "Anonymous Testcase" |
| end |
| |
| local tc = TestCase(name) |
| for index, test in ipairs({...}) 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 |
| |
| |
| |
| |