Module:UnitTests: Difference between revisions

Content deleted Content added
prevent insert function from inserting nil and causing table.concat to complain
add <h2> tags to tests to allow the ToC to appear
 
(26 intermediate revisions by 11 users not shown)
Line 3:
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 }
Line 28 ⟶ 29:
 
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)
Line 40 ⟶ 43:
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)
if actual == expected then
result_table:insert('| ', tick)
else
result_table:insert('| ', cross)
num_failures = num_failures + 1
end
local maybe_nowiki = (options and options.nowiki) and mw.text.nowiki or return_varargs
local differs_at = self.differs_at and (' \n| ' .. first_difference(expected, actual)) or ''
result_table:insert(' \n| ', mw.text.nowiki(text), ' \n| ',
maybe_nowiki(expected), ' \n| ', maybe_nowiki(actual), differs_at,
"\n|-\n")
end
 
Line 64 ⟶ 118:
 
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)
if actual == expected then
end
result_table:insert('| ', tick)
 
else
function UnitTester:preprocess_equals_compare(live, sandbox, expected, options)
result_table:insert('| ', cross)
local live_text = frame:preprocess(live)
num_failures = num_failures + 1
local sandbox_text = frame:preprocess(sandbox)
end
local highlight_live = false
local maybe_nowiki = (options and options.nowiki) and mw.text.nowiki or return_varargs
local highlight_sandbox = false
local differs_at = self.differs_at and (' \n| ' .. first_difference(expected, actual)) or ''
num_runs = num_runs + 1
result_table:insert(' \n| ', mw.text.nowiki(text1), ' \n| ',
if live_text == expected and sandbox_text == expected then
maybe_nowiki(expected), ' \n| ', maybe_nowiki(actual), differs_at,
result_table:insert('| ', tick)
"\n|-\n")
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
 
Line 82 ⟶ 162:
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)
Line 92 ⟶ 181:
num_failures = num_failures + 1
end
local maybe_nowikiformatting = (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 function(x) return x endreturn_varargs
result_table:insert(' \n| ', name, ' \n| ',
maybe_nowikiformatting(tostring(display(expected))), ' \n| ',
maybe_nowikiformatting(tostring(display(actual))), differs_at, "\n|-\n")
end
 
Line 121 ⟶ 210:
end
 
local function val_to_str(vobj)
local function table_key_to_str(k)
if type(v) == 'string' then
vif type(k) == 'string' and mw.ustring.gsubmatch(vk, '\n', '\\n^[_%a][_%a%d]*$') then
return k
if mw.ustring.match(mw.ustring.gsub(v, '[^\'"]', ''), '^"+$') then
return "'" .. v .. "'"else
return '[' .. val_to_str(k) .. ']'
end
return '"' .. mw.ustring.gsub(v, '"', '\\"' ) .. '"'
else
return type(v) == 'table' and table_to_str(v) or tostring(v)
end
end
 
if type(obj) == "string" then
function table_key_to_str(k)
if type(k) == 'string' andobj = mw.ustring.matchgsub(kobj, '^[_%a][_%a%d]*$')"\n", then"\\n")
if mw.ustring.match(mw.ustring.gsub(obj, '[^\'"]', ''), '^"+$') then
return k
return "'" .. obj .. "'"
else
end
return '[' .. val_to_str(k) .. ']'
return '"' .. mw.ustring.gsub(obj, '"', '\\"' ) .. '"'
end
end
 
elseif type(obj) == "table" then
function table_to_str(tbl)
local result, donechecked = {}, {}
for k, v in ipairs(tblobj) do
table.insert(result, val_to_str(v))
done checked[k] = true
end
for k, v in pairs(tbl) do
if not done[k] then
table.insert(result, table_key_to_str(k) .. '=' .. val_to_str(v))
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
return '{' .. table.concat(result, ',') .. '}'
end
 
function UnitTester:equals_deep(name, actual, expected, options)
num_runs = num_runs + 1
if deep_compare(actual, expected) then
result_table:insert('| ', tick)
Line 162 ⟶ 252:
num_failures = num_failures + 1
end
local maybe_nowikiformatting = (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| ', maybe_nowikiformatting(expected_str),
' \n| ', maybe_nowikiformatting(actual_str), differs_at, "\n|-\n")
end
 
Line 178 ⟶ 268:
:format(type(func)), 2)
end
 
for i, example in ipairs(examples) do
if type(example) == 'table' then
Line 203 ⟶ 293:
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
Line 212 ⟶ 309:
-- Sort results into alphabetical order.
local self_sorted = {}
for key,value _ in pairs(self) do
if key:find('^test') then
table.insert(self_sorted, key)
Line 219 ⟶ 316:
table.sort(self_sorted)
-- Add results to the results table.
for i_, value in ipairs(self_sorted) do
result_table:insert_format("<h2>%s</h2>\n", value)
result_table:insert_format(table_header .. "\n|-\n", value)
self[value](self)
result_table:insert("|}\n\n")
end
 
return (num_runs == 0 and "<b>No tests were run.</b>"
return (num_failures == 0 and "<b style=\"color:#008000\">All tests passed.</b>" or "<b style=\"color:#800000\">'''" .. num_failures .. " tests failed.</b>") .. "\n\n" .. frame:preprocess(result_table:concat())
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