Module:UnitTests: Difference between revisions

Jump to navigation Jump to search
no edit summary
No edit summary
No edit summary
 
Line 1: Line 1:
local m_table = require("Module:table")
local UnitTester = {}
local UnitTester = {}


local require = require
local concat = table.concat
local concat = table.concat
local deep_equals = m_table.deepEquals
local deep_equals = require("Module:table/deepEquals")
local error = error
local explode_utf8 = require("Module:string utilities").explode_utf8
local explode_utf8 = require("Module:string utilities").explode_utf8
local find = string.find
local full_url = mw.uri.fullUrl
local gsub = string.gsub
local html = mw.html
local html = mw.html
local insert = table.insert
local insert = table.insert
local ipairs = ipairs
local is_callable = require("Module:fun/isCallable")
local is_combining = require("Module:Unicode data").is_combining
local is_combining = require("Module:Unicode data").is_combining
local match = string.match
local nowiki = require("Module:string/nowiki")
local nowiki = require("Module:string/nowiki")
local shallow_copy = m_table.shallowCopy
local pairs = pairs
local shallow_copy = require("Module:table/shallowCopy")
local sort = table.sort
local sort = table.sort
local sorted_pairs = m_table.sortedPairs
local sorted_pairs = require("Module:table/sortedPairs")
local ustring = mw.ustring
local sub = string.sub
local tostring = tostring
local traceback = debug.traceback
local type = type
local umatch = mw.ustring.find
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local usub = mw.ustring.sub
local xpcall = require("Module:fun/xpcall")
 
local current_title = mw.title.getCurrentTitle()


local tick, cross =
local tick, cross =
Line 38: Line 54:


local function highlight(str)
local function highlight(str)
if ustring.find(str, "%s") then
if umatch(str, "%s") then
return '<span style="background-color: pink;">' ..
return '<span style="background-color: var(--wikt-palette-red-4,pink);">' ..
string.gsub(str, " ", "&nbsp;") .. '</span>'
gsub(str, " ", "&nbsp;") .. '</span>'
else
else
return '<span style="color: red;">' ..
return '<span style="color: var(--wikt-palette-red-9, red);">' ..
str .. '</span>'
str .. '</span>'
end
end
Line 48: Line 64:


local function find_noncombining(str, i, incr)
local function find_noncombining(str, i, incr)
local char = ustring.sub(str, i, i)
while true do
while char ~= '' and is_combining(char) do
local ch = usub(str, i, i)
if ch == "" or not is_combining(ch) then
return i
end
i = i + incr
i = i + incr
char = ustring.sub(str, i, i)
end
end
return i
end
end


Line 68: Line 85:
local j = find_noncombining(actual, differs_at + 1, 1)
local j = find_noncombining(actual, differs_at + 1, 1)
j = j - 1
j = j - 1
return ustring.sub(actual, 1, i - 1) ..
return usub(actual, 1, i - 1) ..
(type(func) == "function" and func or highlight)(ustring.sub(actual, i, j)) ..
(is_callable(func) and func or highlight)(usub(actual, i, j)) ..
ustring.sub(actual, j + 1, -1)
usub(actual, j + 1, -1)
end
end


local function val_to_str(v)
local function val_to_str(v)
if type(v) == 'string' then
if type(v) == "string" then
v = string.gsub(v, '\n', '\\n')
v = gsub(v, '\n', '\\n')
if string.find(string.gsub(v, '[^\'"]', ''), '^"+$') then
if find(gsub(v, '[^\'"]', ''), '^"+$') then
return "'" .. v .. "'"
return "'" .. v .. "'"
end
end
return '"' .. string.gsub(v, '"', '\\"' ) .. '"'
return '"' .. gsub(v, '"', '\\"' ) .. '"'
elseif type(v) == 'table' then
elseif type(v) == 'table' then
local result, done = {}, {}
local result, done = {}, {}
Line 88: Line 105:
for k, val in sorted_pairs(v) do
for k, val in sorted_pairs(v) do
if not done[k] then
if not done[k] then
if (type(k) ~= "string") or not string.find(k, '^[_%a][_%a%d]*$') then
if (type(k) ~= "string") or not find(k, '^[_%a][_%a%d]*$') then
k = '[' .. val_to_str(k) .. ']'
k = '[' .. val_to_str(k) .. ']'
end
end
Line 112: Line 129:
local ty1 = type(t1)
local ty1 = type(t1)
if not (ty1 == type(t2) and ty1 == "table") then
if not (ty1 == type(t2) and ty1 == "table") then
return nil
end
local mt = getmetatable(t1)
if mt and type(mt) == "table" and rawget(mt, "__eq") then
return nil
return nil
end
end
Line 127: Line 139:
end
end


local function extract_keys(table, keys)
local function extract_keys(t, keys)
if not keys then return table end
if not keys then
local new_table = {}
return t
end
local new_t = {}
for _, key in ipairs(keys) do
for _, key in ipairs(keys) do
new_table[key] = table[key]
new_t[key] = t[key]
end
end
return new_table
return new_t
end
end


Line 148: Line 162:
insert(columns, "Expected")
insert(columns, "Expected")
insert(columns, "Actual")
insert(columns, "Actual")
insert(columns, differs_at)
if self.differs_at then
if self.differs_at then
Line 169: Line 182:
:attr("class", "unit-tests wikitable")
:attr("class", "unit-tests wikitable")
:node(header_row)
:node(header_row)
end
function UnitTester:get_result(key)
return self[key](self)
end
end


Line 183: Line 200:
end
end
if options and type(options.display) == "function" then
if options and is_callable(options.display) then
expected = options.display(expected)
expected = options.display(expected)
actual = options.display(actual)
actual = options.display(actual)
Line 227: Line 244:


function UnitTester:equals(name, actual, expected, options)
function UnitTester:equals(name, actual, expected, options)
success = actual == expected
local success = actual == expected
if options and options.show_difference then
if options and options.show_difference then
local difference = first_difference(expected, actual)
local difference = first_difference(expected, actual)
if type(difference) == "number" then
if type(difference) == "number" then
actual = highlight_difference(actual, expected, difference,
actual = highlight_difference(actual, expected, difference,
type(options.show_difference) == "function" and options.show_difference)
is_callable(options.show_difference) and options.show_difference)
end
end
end
end
Line 285: Line 302:
function UnitTester:iterate(examples, func)
function UnitTester:iterate(examples, func)
require 'libraryUtil'.checkType('iterate', 1, examples, 'table')
require 'libraryUtil'.checkType('iterate', 1, examples, 'table')
if type(func) == 'string' then
if type(func) == "string" then
func = self[func]
func = self[func]
elseif type(func) ~= 'function' then
elseif not is_callable(func) then
error(("bad argument #2 to 'iterate' (expected function or string, got %s)")
error(("bad argument #2 to 'iterate' (expected function, callable table or string; got %s)")
:format(type(func)), 2)
:format(type(func)), 2)
end
end
Line 305: Line 322:


function UnitTester:header(text)
function UnitTester:header(text)
local prefix, maintext = text:match('^#(h[0-9]+):(.*)$')
local prefix, maintext = match(text, '^#(h[0-9]+):(.*)$')
if not prefix then
if not prefix then
maintext = text
maintext = text
Line 325: Line 342:
:node(header)
:node(header)
:done()
:done()
end
local function err_handler(mesg)
return {mesg = mesg, traceback = traceback("", 2)}
end
end


Line 332: Line 353:
local output = {}
local output = {}


local iparams = {
local boolean = {type = "boolean"}
["nowiki"] = {type = "boolean"},
local iargs = require("Module:parameters").process(frame.args, {
["differs_at"] = {type = "boolean"},
["nowiki"] = boolean,
["comments"] = {type = "boolean"},
["differs_at"] = boolean,
["summarize"] = {type = "boolean"},
["comments"] = boolean,
["summarize"] = boolean,
["name_column"] = {list = true, default = "Text"},
["name_column"] = {list = true, default = "Text"},
}
})
 
local iargs = require("Module:parameters").process(frame.args, iparams)


self.frame = frame
self.frame = frame
Line 353: Line 373:
local self_sorted = {}
local self_sorted = {}
for key in pairs(self) do
for key in pairs(self) do
if key:find('^test') then
if sub(key, 1, 4) == "test" then
insert(self_sorted, key)
insert(self_sorted, key)
end
end
Line 367: Line 387:
:wikitext(key .. ":")
:wikitext(key .. ":")
:done()
:done()
local traceback = "(no traceback)"
local success, err = xpcall(UnitTester.get_result, err_handler, self, key)
local success, mesg = xpcall(function()
return self[key](self)
end, function(mesg)
traceback = debug.traceback("", 2)
return mesg
end)
if not success then
if not success then
self.result_table = self.result_table:tag("tr")
self.result_table = self.result_table:tag("tr")
Line 381: Line 395:
:tag("strong")
:tag("strong")
:attr("class", "error")
:attr("class", "error")
:wikitext("Script error during testing: " .. nowiki(mesg))
:wikitext("Script error during testing: " .. nowiki(err.mesg))
:done()
:done()
:wikitext(frame:extensionTag("pre", traceback))
:wikitext(frame:extensionTag("pre", err.traceback or "(no traceback)"))
:allDone()
:allDone()
self.num_failures = self.num_failures + 1
self.num_failures = self.num_failures + 1
Line 390: Line 404:
end
end


local refresh_link = tostring(mw.uri.fullUrl(mw.title.getCurrentTitle().fullText, 'action=purge&forcelinkupdate=1'))
local refresh_link = tostring(full_url(current_title.fullText, 'action=purge&forcelinkupdate=1'))


local failure_cat = '[[Category:Failing testcase modules]]'
local failure_cat = '[[Category:Failing testcase modules]]'
if mw.title.getCurrentTitle().text:find("/documentation$") then
if sub(current_title.text, -14) == "/documentation" then
failure_cat = ''
failure_cat = ''
end
end
Line 420: Line 434:
end
end


local p = UnitTester:new()
local unit_tester = UnitTester:new()
function p.run_tests(frame) return p:run(frame) end
 
return p
function unit_tester.run_tests(frame)
return unit_tester:run(frame)
end
 
return unit_tester

Navigation menu