Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
Help about MediaWiki
Humanipedia
Search
Search
Appearance
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Module:UnitTests
Module
Discussion
English
Read
Edit source
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit source
View history
General
What links here
Related changes
Special pages
Page information
Appearance
move to sidebar
hide
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
-- UnitTester provides unit testing for other Lua scripts. For details see [[Wikipedia:Lua#Unit_testing]]. -- For user documentation see talk page. local UnitTester = {} local frame, tick, cross, should_highlight local result_table_header = "{|class=\"wikitable unit-tests-result\"\n|+ %s\n! !! Text !! Expected !! Actual" local result_table_live_sandbox_header = "{|class=\"wikitable unit-tests-result\"\n|+ %s\n! !! Test !! Live !! Sandbox !! Expected" local result_table = { n = 0 } local result_table_mt = { insert = function (self, ...) local n = self.n for i = 1, select('#', ...) do local val = select(i, ...) if val ~= nil then n = n + 1 self[n] = val end end self.n = n end, insert_format = function (self, ...) self:insert(string.format(...)) end, concat = table.concat } result_table_mt.__index = result_table_mt setmetatable(result_table, result_table_mt) local num_failures = 0 local num_runs = 0 local function first_difference(s1, s2) s1, s2 = tostring(s1), tostring(s2) if s1 == s2 then return '' end local max = math.min(#s1, #s2) for i = 1, max do if s1:sub(i,i) ~= s2:sub(i,i) then return i end end return max + 1 end local function return_varargs(...) return ... end function UnitTester:calculate_output(text, expected, actual, options) -- Set up some variables for throughout for ease num_runs = num_runs + 1 local options = options or {} -- Fix any stripmarkers if asked to do so to prevent incorrect fails local compared_expected = expected local compared_actual = actual if options.templatestyles then local pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-)(%x+)(%-QINU[^\127]*\127)' local _, expected_stripmarker_id = compared_expected:match(pattern) -- when module rendering has templatestyles strip markers, use ID from expected to prevent false test fail if expected_stripmarker_id then compared_actual = compared_actual:gsub(pattern, '%1' .. expected_stripmarker_id .. '%3') -- replace actual id with expected id; ignore second capture in pattern compared_expected = compared_expected:gsub(pattern, '%1' .. expected_stripmarker_id .. '%3') -- account for other strip markers end end if options.stripmarker then local pattern = '(\127[^\127]*UNIQ%-%-%l+%-)(%x+)(%-%-?QINU[^\127]*\127)' local _, expected_stripmarker_id = compared_expected:match(pattern) if expected_stripmarker_id then compared_actual = compared_actual:gsub(pattern, '%1' .. expected_stripmarker_id .. '%3') compared_expected = compared_expected:gsub(pattern, '%1' .. expected_stripmarker_id .. '%3') end end -- Perform the comparison local success = compared_actual == compared_expected if not success then num_failures = num_failures + 1 end -- Sort the wikitext for displaying the results if options.combined then -- We need 2 rows available for the expected and actual columns -- Top one is parsed, bottom is unparsed local differs_at = self.differs_at and (' \n| rowspan=2|' .. first_difference(compared_expected, compared_actual)) or '' -- Local copies of tick/cross to allow for highlighting local highlight = (should_highlight and not success and 'style="background:#fc0;" ') or '' result_table:insert( -- Start output '| ', highlight, 'rowspan=2|', success and tick or cross, -- Tick/Cross (2 rows) ' \n| rowspan=2|', mw.text.nowiki(text), ' \n| ', -- Text used for the test (2 rows) expected, ' \n| ', actual, -- The parsed outputs (in the 1st row) differs_at, ' \n|-\n| ', -- Where any relevant difference was (2 rows) mw.text.nowiki(expected), ' \n| ', mw.text.nowiki(actual), -- The unparsed outputs (in the 2nd row) '\n|-\n' -- End output ) else -- Display normally with whichever option was preferred (nowiki/parsed) local differs_at = self.differs_at and (' \n| ' .. first_difference(compared_expected, compared_actual)) or '' local formatting = options.nowiki and mw.text.nowiki or return_varargs local highlight = (should_highlight and not success and 'style="background:#fc0;"|') or '' result_table:insert( -- Start output '| ', highlight, success and tick or cross, -- Tick/Cross ' \n| ', mw.text.nowiki(text), ' \n| ', -- Text used for the test formatting(expected), ' \n| ', formatting(actual), -- The formatted outputs differs_at, -- Where any relevant difference was '\n|-\n' -- End output ) end end function UnitTester:preprocess_equals(text, expected, options) local actual = frame:preprocess(text) self:calculate_output(text, expected, actual, options) end function UnitTester:preprocess_equals_many(prefix, suffix, cases, options) for _, case in ipairs(cases) do self:preprocess_equals(prefix .. case[1] .. suffix, case[2], options) end end function UnitTester:preprocess_equals_preprocess(text1, text2, options) local actual = frame:preprocess(text1) local expected = frame:preprocess(text2) self:calculate_output(text1, expected, actual, options) end function UnitTester:preprocess_equals_compare(live, sandbox, expected, options) local live_text = frame:preprocess(live) local sandbox_text = frame:preprocess(sandbox) local highlight_live = false local highlight_sandbox = false num_runs = num_runs + 1 if live_text == expected and sandbox_text == expected then result_table:insert('| ', tick) else result_table:insert('| ', cross) num_failures = num_failures + 1 if live_text ~= expected then highlight_live = true end if sandbox_text ~= expected then highlight_sandbox = true end end local formatting = (options and options.nowiki and mw.text.nowiki) or return_varargs local differs_at = self.differs_at and (' \n| ' .. first_difference(expected, live_text) or first_difference(expected, sandbox_text)) or '' result_table:insert( ' \n| ', mw.text.nowiki(live), should_highlight and highlight_live and ' \n|style="background: #fc0;"| ' or ' \n| ', formatting(live_text), should_highlight and highlight_sandbox and ' \n|style="background: #fc0;"| ' or ' \n| ', formatting(sandbox_text), ' \n| ', formatting(expected), differs_at, "\n|-\n" ) end function UnitTester:preprocess_equals_preprocess_many(prefix1, suffix1, prefix2, suffix2, cases, options) for _, case in ipairs(cases) do self:preprocess_equals_preprocess(prefix1 .. case[1] .. suffix1, prefix2 .. (case[2] and case[2] or case[1]) .. suffix2, options) end end function UnitTester:preprocess_equals_sandbox_many(module, function_name, cases, options) for _, case in ipairs(cases) do local live = module .. "|" .. function_name .. "|" .. case[1] .. "}}" local sandbox = module .. "/sandbox|" .. function_name .. "|" .. case[1] .. "}}" self:preprocess_equals_compare(live, sandbox, case[2], options) end end function UnitTester:equals(name, actual, expected, options) num_runs = num_runs + 1 if actual == expected then result_table:insert('| ', tick) else result_table:insert('| ', cross) num_failures = num_failures + 1 end local formatting = (options and options.nowiki and mw.text.nowiki) or return_varargs local differs_at = self.differs_at and (' \n| ' .. first_difference(expected, actual)) or '' local display = options and options.display or return_varargs result_table:insert(' \n| ', name, ' \n| ', formatting(tostring(display(expected))), ' \n| ', formatting(tostring(display(actual))), differs_at, "\n|-\n") end local function deep_compare(t1, t2, ignore_mt) local ty1 = type(t1) local ty2 = type(t2) if ty1 ~= ty2 then return false end if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end local mt = getmetatable(t1) if not ignore_mt and mt and mt.__eq then return t1 == t2 end for k1, v1 in pairs(t1) do local v2 = t2[k1] if v2 == nil or not deep_compare(v1, v2) then return false end end for k2, v2 in pairs(t2) do local v1 = t1[k2] if v1 == nil or not deep_compare(v1, v2) then return false end end return true end local function val_to_str(obj) local function table_key_to_str(k) if type(k) == 'string' and mw.ustring.match(k, '^[_%a][_%a%d]*$') then return k else return '[' .. val_to_str(k) .. ']' end end if type(obj) == "string" then obj = mw.ustring.gsub(obj, "\n", "\\n") if mw.ustring.match(mw.ustring.gsub(obj, '[^\'"]', ''), '^"+$') then return "'" .. obj .. "'" end return '"' .. mw.ustring.gsub(obj, '"', '\\"' ) .. '"' elseif type(obj) == "table" then local result, checked = {}, {} for k, v in ipairs(obj) do table.insert(result, val_to_str(v)) checked[k] = true end for k, v in pairs(obj) do if not checked[k] then table.insert(result, table_key_to_str(k) .. '=' .. val_to_str(v)) end end return '{' .. table.concat(result, ',') .. '}' else return tostring(obj) end end function UnitTester:equals_deep(name, actual, expected, options) num_runs = num_runs + 1 if deep_compare(actual, expected) then result_table:insert('| ', tick) else result_table:insert('| ', cross) num_failures = num_failures + 1 end local formatting = (options and options.nowiki and mw.text.nowiki) or return_varargs local actual_str = val_to_str(actual) local expected_str = val_to_str(expected) local differs_at = self.differs_at and (' \n| ' .. first_difference(expected_str, actual_str)) or '' result_table:insert(' \n| ', name, ' \n| ', formatting(expected_str), ' \n| ', formatting(actual_str), differs_at, "\n|-\n") end function UnitTester:iterate(examples, func) require 'libraryUtil'.checkType('iterate', 1, examples, 'table') if type(func) == 'string' then func = self[func] elseif type(func) ~= 'function' then error(("bad argument #2 to 'iterate' (expected function or string, got %s)") :format(type(func)), 2) end for i, example in ipairs(examples) do if type(example) == 'table' then func(self, unpack(example)) elseif type(example) == 'string' then self:heading(example) else error(('bad example #%d (expected table, got %s)') :format(i, type(example)), 2) end end end function UnitTester:heading(text) result_table:insert_format(' ! colspan="%u" style="text-align: left" | %s \n |- \n ', self.columns, text) end function UnitTester:run(frame_arg) frame = frame_arg self.frame = frame self.differs_at = frame.args['differs_at'] tick = frame:preprocess('{{Tick}}') cross = frame:preprocess('{{Cross}}') local table_header = result_table_header if frame.args['live_sandbox'] then table_header = result_table_live_sandbox_header end if frame.args.highlight then should_highlight = true end self.columns = 4 if self.differs_at then table_header = table_header .. ' !! Differs at' self.columns = self.columns + 1 end -- Sort results into alphabetical order. local self_sorted = {} for key, _ in pairs(self) do if key:find('^test') then table.insert(self_sorted, key) end end table.sort(self_sorted) -- Add results to the results table. for _, value in ipairs(self_sorted) do result_table:insert_format(table_header .. "\n|-\n", value) self[value](self) result_table:insert("|}\n") end return (num_runs == 0 and "<b>No tests were run.</b>" or num_failures == 0 and "<b style=\"color:#008000\">All " .. num_runs .. " tests passed.</b>" or "<b style=\"color:#800000\">" .. num_failures .. " of " .. num_runs .. " tests failed.</b>[[Category:Failed Lua testcases using Module:UnitTests]]" ) .. "\n\n" .. frame:preprocess(result_table:concat()) end function UnitTester:new() local o = {} setmetatable(o, self) self.__index = self return o end local p = UnitTester:new() function p.run_tests(frame) return p:run(frame) end return p
Summary:
Please note that all contributions to Humanipedia may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
Humanipedia:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Templates used on this page:
Template:Code
(
edit
)
Template:Cross
(
edit
)
Template:Hatnote
(
edit
)
Template:Hello
(
edit
)
Template:Spellnum', '
(
edit
)
Template:Tag
(
edit
)
Template:Tick
(
edit
)
Module:Arguments
(
edit
)
Module:ConvertNumeric
(
edit
)
Module:Example
(
edit
)
Module:Example/testcases
(
edit
)
Module:Hatnote
(
edit
)
Module:Hatnote/styles.css
(
edit
)
Module:UnitTests
(
edit
)
Module:UnitTests/doc
(
edit
)