48,355
edits
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
local UnitTester = {} | local UnitTester = {} | ||
local require = require | |||
local concat = table.concat | local concat = table.concat | ||
local deep_equals = | 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 = | local pairs = pairs | ||
local shallow_copy = require("Module:table/shallowCopy") | |||
local sort = table.sort | local sort = table.sort | ||
local sorted_pairs = | 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 | if umatch(str, "%s") then | ||
return '<span style="background-color: pink;">' .. | return '<span style="background-color: var(--wikt-palette-red-4,pink);">' .. | ||
gsub(str, " ", " ") .. '</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 | while true do | ||
local ch = usub(str, i, i) | |||
if ch == "" or not is_combining(ch) then | |||
return i | |||
end | |||
i = i + incr | i = i + incr | ||
end | end | ||
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 | return usub(actual, 1, i - 1) .. | ||
( | (is_callable(func) and func or highlight)(usub(actual, i, j)) .. | ||
usub(actual, j + 1, -1) | |||
end | end | ||
local function val_to_str(v) | local function val_to_str(v) | ||
if type(v) == | if type(v) == "string" then | ||
v = | v = gsub(v, '\n', '\\n') | ||
if | if find(gsub(v, '[^\'"]', ''), '^"+$') then | ||
return "'" .. v .. "'" | return "'" .. v .. "'" | ||
end | end | ||
return '"' . | 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 | 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 | return nil | ||
end | end | ||
| Line 127: | Line 139: | ||
end | end | ||
local function extract_keys( | local function extract_keys(t, keys) | ||
if not keys then return | if not keys then | ||
local | return t | ||
end | |||
local new_t = {} | |||
for _, key in ipairs(keys) do | for _, key in ipairs(keys) do | ||
new_t[key] = t[key] | |||
end | end | ||
return | return new_t | ||
end | end | ||
| Line 148: | Line 162: | ||
insert(columns, "Expected") | insert(columns, "Expected") | ||
insert(columns, "Actual") | insert(columns, "Actual") | ||
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 | 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, | ||
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) == | if type(func) == "string" then | ||
func = self[func] | func = self[func] | ||
elseif | elseif not is_callable(func) then | ||
error(("bad argument #2 to 'iterate' (expected function or string | 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 = | 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 | local boolean = {type = "boolean"} | ||
["nowiki"] = | local iargs = require("Module:parameters").process(frame.args, { | ||
["differs_at"] = | ["nowiki"] = boolean, | ||
["comments"] = | ["differs_at"] = boolean, | ||
["summarize"] = | ["comments"] = boolean, | ||
["summarize"] = boolean, | |||
["name_column"] = {list = true, default = "Text"}, | ["name_column"] = {list = true, default = "Text"}, | ||
} | }) | ||
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 | 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 success, err = xpcall(UnitTester.get_result, err_handler, self, key) | |||
local success, | |||
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( | 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 | if sub(current_title.text, -14) == "/documentation" then | ||
failure_cat = '' | failure_cat = '' | ||
end | end | ||
| Line 420: | Line 434: | ||
end | end | ||
local | local unit_tester = UnitTester:new() | ||
function | |||
return | function unit_tester.run_tests(frame) | ||
return unit_tester:run(frame) | |||
end | |||
return unit_tester | |||