<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://mywikibiz.com/index.php?action=history&amp;feed=atom&amp;title=Module%3AUnitTests</id>
	<title>Module:UnitTests - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://mywikibiz.com/index.php?action=history&amp;feed=atom&amp;title=Module%3AUnitTests"/>
	<link rel="alternate" type="text/html" href="https://mywikibiz.com/index.php?title=Module:UnitTests&amp;action=history"/>
	<updated>2026-06-13T19:17:00Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.35.3</generator>
	<entry>
		<id>https://mywikibiz.com/index.php?title=Module:UnitTests&amp;diff=479909&amp;oldid=prev</id>
		<title>Zoran: Pywikibot 6.4.0</title>
		<link rel="alternate" type="text/html" href="https://mywikibiz.com/index.php?title=Module:UnitTests&amp;diff=479909&amp;oldid=prev"/>
		<updated>2021-07-16T07:47:23Z</updated>

		<summary type="html">&lt;p&gt;Pywikibot 6.4.0&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- UnitTester provides unit testing for other Lua scripts. For details see [[Wikipedia:Lua#Unit_testing]].&lt;br /&gt;
-- For user documentation see talk page.&lt;br /&gt;
local UnitTester = {}&lt;br /&gt;
&lt;br /&gt;
local frame, tick, cross&lt;br /&gt;
local result_table_header = &amp;quot;{|class=\&amp;quot;wikitable\&amp;quot;\n|+ %s\n! !! Text !! Expected !! Actual&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local result_table = { n = 0 }&lt;br /&gt;
local result_table_mt = {&lt;br /&gt;
	insert = function (self, ...)&lt;br /&gt;
		local n = self.n&lt;br /&gt;
		for i = 1, select('#', ...) do&lt;br /&gt;
			local val = select(i, ...)&lt;br /&gt;
			if val ~= nil then&lt;br /&gt;
				n = n + 1&lt;br /&gt;
				self[n] = val&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		self.n = n&lt;br /&gt;
	end,&lt;br /&gt;
	insert_format = function (self, ...)&lt;br /&gt;
		self:insert(string.format(...))&lt;br /&gt;
	end,&lt;br /&gt;
	concat = table.concat&lt;br /&gt;
}&lt;br /&gt;
result_table_mt.__index = result_table_mt&lt;br /&gt;
setmetatable(result_table, result_table_mt)&lt;br /&gt;
&lt;br /&gt;
local num_failures = 0&lt;br /&gt;
&lt;br /&gt;
function first_difference(s1, s2)&lt;br /&gt;
    if s1 == s2 then return '' end&lt;br /&gt;
    local max = math.min(#s1, #s2)&lt;br /&gt;
    for i = 1, max do&lt;br /&gt;
        if s1:sub(i,i) ~= s2:sub(i,i) then return i end&lt;br /&gt;
    end&lt;br /&gt;
    return max + 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function return_varargs(...)&lt;br /&gt;
	return ...&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function UnitTester:preprocess_equals(text, expected, options)&lt;br /&gt;
    local actual = frame:preprocess(text)&lt;br /&gt;
    if actual == expected then&lt;br /&gt;
        result_table:insert('| ', tick)&lt;br /&gt;
    else&lt;br /&gt;
        result_table:insert('| ', cross)&lt;br /&gt;
        num_failures = num_failures + 1&lt;br /&gt;
    end&lt;br /&gt;
    local maybe_nowiki = (options and options.nowiki) and mw.text.nowiki or return_varargs&lt;br /&gt;
    local differs_at = self.differs_at and (' \n| ' .. first_difference(expected, actual)) or ''&lt;br /&gt;
    result_table:insert(' \n| ', mw.text.nowiki(text), ' \n| ',&lt;br /&gt;
    	maybe_nowiki(expected), ' \n| ', maybe_nowiki(actual), differs_at,&lt;br /&gt;
    	&amp;quot;\n|-\n&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function UnitTester:preprocess_equals_many(prefix, suffix, cases, options)&lt;br /&gt;
    for _, case in ipairs(cases) do&lt;br /&gt;
        self:preprocess_equals(prefix .. case[1] .. suffix, case[2], options)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function UnitTester:preprocess_equals_preprocess(text1, text2, options)&lt;br /&gt;
	local actual = frame:preprocess(text1)										-- &amp;lt;actual&amp;gt; and &amp;lt;expected&amp;gt; will be rendered in &amp;lt;result_table&amp;gt; &lt;br /&gt;
	local expected = frame:preprocess(text2)&lt;br /&gt;
	local actual_for_comp = actual;												-- copy of &amp;lt;actual&amp;gt; that may be modified to have same templatestyles stripmarker as &amp;lt;expected&amp;gt; for comparison&lt;br /&gt;
	local highlight;															-- boolean true to highlight mismatched renderings in Actual column of result&lt;br /&gt;
&lt;br /&gt;
	if options and true == options.templatestyles then							-- when module rendering has templatestyles strip markers, use ID from expected to prevent false test fail&lt;br /&gt;
		local pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-)(%x+)(%-QINU[^\127]*\127)';	-- templatestyle stripmarker pattern&lt;br /&gt;
		local _, expected_stripmarker_id = expected:match (pattern);			-- get templatestyles strip marker id from expected (the reference); ignore first capture in pattern&lt;br /&gt;
		&lt;br /&gt;
		if expected_stripmarker_id then&lt;br /&gt;
			actual_for_comp = actual_for_comp:gsub (pattern, '%1'..expected_stripmarker_id..'%3');	-- replace actual id with expected id; ignore second capture in pattern&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- option to ignore any strip marker when comparing actual to expected&lt;br /&gt;
	if options and true == options.stripmarker then&lt;br /&gt;
		local pattern = '(\127[^\127]*UNIQ%-%-%l+%-)(%x+)(%-QINU[^\127]*\127)';&lt;br /&gt;
		local _, expected_stripmarker_id = expected:match (pattern);&lt;br /&gt;
		if expected_stripmarker_id then&lt;br /&gt;
			actual_for_comp = actual_for_comp:gsub (pattern, '%1'..expected_stripmarker_id..'%3');&lt;br /&gt;
			expected = expected:gsub (pattern, '%1'..expected_stripmarker_id..'%3');&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if actual_for_comp == expected then											-- are the renderings the same&lt;br /&gt;
		result_table:insert('| ', tick)&lt;br /&gt;
	else&lt;br /&gt;
		result_table:insert('| ', cross)										-- nope; mark as failure&lt;br /&gt;
		num_failures = num_failures + 1											-- tally&lt;br /&gt;
		highlight=true															-- and highlight the results table Actual column cell (so the failed test is more obvious)&lt;br /&gt;
	end&lt;br /&gt;
	local maybe_nowiki = (options and options.nowiki) and mw.text.nowiki or return_varargs&lt;br /&gt;
	local differs_at = self.differs_at and (' \n| ' .. first_difference(expected, actual)) or ''&lt;br /&gt;
	result_table:insert(' \n| ',&lt;br /&gt;
		mw.text.nowiki(text1),													-- the tested template nowikied&lt;br /&gt;
		' \n| ',&lt;br /&gt;
		maybe_nowiki(expected),													-- the expected version&lt;br /&gt;
		highlight and ' \n|style=&amp;quot;background: #fc0&amp;quot;| ' or ' \n| ',				-- highlighted if test fail; not else&lt;br /&gt;
		maybe_nowiki(actual), differs_at,										-- the actual rendering using its own template styles if that is an option&lt;br /&gt;
		&amp;quot;\n|-\n&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function UnitTester:preprocess_equals_preprocess_many(prefix1, suffix1, prefix2, suffix2, cases, options)&lt;br /&gt;
    for _, case in ipairs(cases) do&lt;br /&gt;
        self:preprocess_equals_preprocess(prefix1 .. case[1] .. suffix1, prefix2 .. (case[2] and case[2] or case[1]) .. suffix2, options)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function UnitTester:equals(name, actual, expected, options)&lt;br /&gt;
    if actual == expected then&lt;br /&gt;
        result_table:insert('| ', tick)&lt;br /&gt;
    else&lt;br /&gt;
        result_table:insert('| ', cross)&lt;br /&gt;
        num_failures = num_failures + 1&lt;br /&gt;
    end&lt;br /&gt;
    local maybe_nowiki = (options and options.nowiki) and mw.text.nowiki or return_varargs&lt;br /&gt;
    local differs_at = self.differs_at and (' \n| ' .. first_difference(expected, actual)) or ''&lt;br /&gt;
    local display = options and options.display or function(x) return x end&lt;br /&gt;
    result_table:insert(' \n| ', name, ' \n| ',&lt;br /&gt;
    	maybe_nowiki(tostring(display(expected))), ' \n| ',&lt;br /&gt;
    	maybe_nowiki(tostring(display(actual))), differs_at, &amp;quot;\n|-\n&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function deep_compare(t1, t2, ignore_mt)&lt;br /&gt;
    local ty1 = type(t1)&lt;br /&gt;
    local ty2 = type(t2)&lt;br /&gt;
    if ty1 ~= ty2 then return false end&lt;br /&gt;
    if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end&lt;br /&gt;
&lt;br /&gt;
    local mt = getmetatable(t1)&lt;br /&gt;
    if not ignore_mt and mt and mt.__eq then return t1 == t2 end&lt;br /&gt;
&lt;br /&gt;
    for k1, v1 in pairs(t1) do&lt;br /&gt;
        local v2 = t2[k1]&lt;br /&gt;
        if v2 == nil or not deep_compare(v1, v2) then return false end&lt;br /&gt;
    end&lt;br /&gt;
    for k2, v2 in pairs(t2) do&lt;br /&gt;
        local v1 = t1[k2]&lt;br /&gt;
        if v1 == nil or not deep_compare(v1, v2) then return false end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function val_to_str(v)&lt;br /&gt;
    if type(v) == 'string' then&lt;br /&gt;
        v = mw.ustring.gsub(v, '\n', '\\n')&lt;br /&gt;
        if mw.ustring.match(mw.ustring.gsub(v, '[^\'&amp;quot;]', ''), '^&amp;quot;+$') then&lt;br /&gt;
            return &amp;quot;'&amp;quot; .. v .. &amp;quot;'&amp;quot;&lt;br /&gt;
        end&lt;br /&gt;
        return '&amp;quot;' .. mw.ustring.gsub(v, '&amp;quot;', '\\&amp;quot;' ) .. '&amp;quot;'&lt;br /&gt;
    else&lt;br /&gt;
        return type(v) == 'table' and table_to_str(v) or tostring(v)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function table_key_to_str(k)&lt;br /&gt;
    if type(k) == 'string' and mw.ustring.match(k, '^[_%a][_%a%d]*$') then&lt;br /&gt;
        return k&lt;br /&gt;
    else&lt;br /&gt;
        return '[' .. val_to_str(k) .. ']'&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function table_to_str(tbl)&lt;br /&gt;
    local result, done = {}, {}&lt;br /&gt;
    for k, v in ipairs(tbl) do&lt;br /&gt;
        table.insert(result, val_to_str(v))&lt;br /&gt;
        done[k] = true&lt;br /&gt;
    end&lt;br /&gt;
    for k, v in pairs(tbl) do&lt;br /&gt;
        if not done[k] then&lt;br /&gt;
            table.insert(result, table_key_to_str(k) .. '=' .. val_to_str(v))&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return '{' .. table.concat(result, ',') .. '}'&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function UnitTester:equals_deep(name, actual, expected, options)&lt;br /&gt;
    if deep_compare(actual, expected) then&lt;br /&gt;
        result_table:insert('| ', tick)&lt;br /&gt;
    else&lt;br /&gt;
        result_table:insert('| ', cross)&lt;br /&gt;
        num_failures = num_failures + 1&lt;br /&gt;
    end&lt;br /&gt;
    local maybe_nowiki = (options and options.nowiki) and mw.text.nowiki or return_varargs&lt;br /&gt;
    local actual_str = val_to_str(actual)&lt;br /&gt;
    local expected_str = val_to_str(expected)&lt;br /&gt;
    local differs_at = self.differs_at and (' \n| ' .. first_difference(expected_str, actual_str)) or ''&lt;br /&gt;
    result_table:insert(' \n| ', name, ' \n| ', maybe_nowiki(expected_str),&lt;br /&gt;
    	' \n| ', maybe_nowiki(actual_str), differs_at, &amp;quot;\n|-\n&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function UnitTester:iterate(examples, func)&lt;br /&gt;
	require 'libraryUtil'.checkType('iterate', 1, examples, 'table')&lt;br /&gt;
	if type(func) == 'string' then&lt;br /&gt;
		func = self[func]&lt;br /&gt;
	elseif type(func) ~= 'function' then&lt;br /&gt;
		error((&amp;quot;bad argument #2 to 'iterate' (expected function or string, got %s)&amp;quot;)&lt;br /&gt;
			:format(type(func)), 2)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	for i, example in ipairs(examples) do&lt;br /&gt;
		if type(example) == 'table' then&lt;br /&gt;
			func(self, unpack(example))&lt;br /&gt;
		elseif type(example) == 'string' then&lt;br /&gt;
			self:heading(example)&lt;br /&gt;
		else&lt;br /&gt;
			error(('bad example #%d (expected table, got %s)')&lt;br /&gt;
				:format(i, type(example)), 2)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function UnitTester:heading(text)&lt;br /&gt;
	result_table:insert_format(' ! colspan=&amp;quot;%u&amp;quot; style=&amp;quot;text-align: left&amp;quot; | %s \n |- \n ',&lt;br /&gt;
		self.columns, text)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function UnitTester:run(frame_arg)&lt;br /&gt;
    frame = frame_arg&lt;br /&gt;
    self.frame = frame&lt;br /&gt;
    self.differs_at = frame.args['differs_at']&lt;br /&gt;
    tick = frame:preprocess('{{Tick}}')&lt;br /&gt;
    cross = frame:preprocess('{{Cross}}')&lt;br /&gt;
&lt;br /&gt;
    local table_header = result_table_header&lt;br /&gt;
	self.columns = 4&lt;br /&gt;
    if self.differs_at then&lt;br /&gt;
        table_header = table_header .. ' !! Differs at'&lt;br /&gt;
        self.columns = self.columns + 1&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Sort results into alphabetical order.&lt;br /&gt;
    local self_sorted = {}&lt;br /&gt;
    for key,value in pairs(self) do&lt;br /&gt;
        if key:find('^test') then&lt;br /&gt;
            table.insert(self_sorted, key)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    table.sort(self_sorted)&lt;br /&gt;
    -- Add results to the results table.&lt;br /&gt;
    for i,value in ipairs(self_sorted) do&lt;br /&gt;
        result_table:insert_format(table_header .. &amp;quot;\n|-\n&amp;quot;, value)&lt;br /&gt;
        self[value](self)&lt;br /&gt;
        result_table:insert(&amp;quot;|}\n&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
	&lt;br /&gt;
    return (num_failures == 0 and &amp;quot;&amp;lt;b style=\&amp;quot;color:#008000\&amp;quot;&amp;gt;All tests passed.&amp;lt;/b&amp;gt;&amp;quot; or &amp;quot;&amp;lt;b style=\&amp;quot;color:#800000\&amp;quot;&amp;gt;&amp;quot; .. num_failures .. &amp;quot; tests failed.&amp;lt;/b&amp;gt;&amp;quot;) .. &amp;quot;\n\n&amp;quot; .. frame:preprocess(result_table:concat())&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function UnitTester:new()&lt;br /&gt;
    local o = {}&lt;br /&gt;
    setmetatable(o, self)&lt;br /&gt;
    self.__index = self&lt;br /&gt;
    return o&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local p = UnitTester:new()&lt;br /&gt;
function p.run_tests(frame) return p:run(frame) end&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Zoran</name></author>
	</entry>
</feed>