Module:UnitTests: Difference between revisions

Content deleted Content added
Failed test cases tracking cat
add <h2> tags to tests to allow the ToC to appear
 
(15 intermediate revisions by 6 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 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 41 ⟶ 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 65 ⟶ 118:
 
function UnitTester:preprocess_equals_preprocess(text1, text2, options)
local actual = frame:preprocess(text1) -- <actual> and <expected> will be rendered in <result_table>
local expected = frame:preprocess(text2)
self:calculate_output(text1, expected, actual, options)
local actual_for_comp = actual -- copy of <actual> that may be modified to have same templatestyles stripmarker as <expected> for comparison
local highlight -- boolean true to highlight mismatched renderings in Actual column of result
 
if options and true == options.templatestyles then -- when module rendering has templatestyles strip markers, use ID from expected to prevent false test fail
local pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-)(%x+)(%-QINU[^\127]*\127)' -- templatestyle stripmarker pattern
local _, expected_stripmarker_id = expected:match(pattern) -- get templatestyles strip marker id from expected (the reference); ignore first capture in pattern
 
if expected_stripmarker_id then
actual_for_comp = actual_for_comp:gsub(pattern, '%1' .. expected_stripmarker_id .. '%3') -- replace actual id with expected id; ignore second capture in pattern
end
end
 
-- option to ignore any strip marker when comparing actual to expected
if options and true == options.stripmarker then
local pattern = '(\127[^\127]*UNIQ%-%-%l+%-)(%x+)(%-QINU[^\127]*\127)'
local _, expected_stripmarker_id = expected:match(pattern)
if expected_stripmarker_id then
actual_for_comp = actual_for_comp:gsub(pattern, '%1' .. expected_stripmarker_id .. '%3')
expected = expected:gsub(pattern, '%1' .. expected_stripmarker_id .. '%3')
end
end
 
if actual_for_comp == expected then -- are the renderings the same
result_table:insert('| ', tick)
else
result_table:insert('| ', cross) -- nope; mark as failure
num_failures = num_failures + 1 -- tally
highlight=true -- and highlight the results table Actual column cell (so the failed test is more obvious)
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(text1), -- the tested template nowikied
' \n| ',
maybe_nowiki(expected), -- the expected version
highlight and ' \n|style="background: #fc0;"| ' or ' \n| ', -- highlighted if test fail; not else
maybe_nowiki(actual), differs_at, -- the actual rendering using its own template styles if that is an option
"\n|-\n")
end
 
Line 110 ⟶ 126:
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)
Line 127 ⟶ 143:
end
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, 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| ',
maybe_nowikiformatting(live_text),
should_highlight and highlight_sandbox and ' \n|style="background: #fc0;"| ' or ' \n| ',
maybe_nowikiformatting(sandbox_text),
' \n| ',
maybe_nowikiformatting(expected),
differs_at,
"\n|-\n"
Line 158 ⟶ 174:
 
function UnitTester:equals(name, actual, expected, options)
num_runs = num_runs + 1
if actual == expected then
result_table:insert('| ', tick)
Line 164 ⟶ 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 193 ⟶ 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 234 ⟶ 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 278 ⟶ 296:
if frame.args['live_sandbox'] then
table_header = result_table_live_sandbox_header
end
if frame.args.highlight then
should_highlight = true
end
 
Line 296 ⟶ 317:
-- Add results to the results table.
for _, 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)
Line 301 ⟶ 323:
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