Module:ScribuntoUnit/sandbox: Difference between revisions

Content deleted Content added
fix parameter name error
use require('strict') instead of require('Module:No globals')
 
(18 intermediate revisions by 4 users not shown)
Line 2:
-- Unit tests for Scribunto.
-------------------------------------------------------------------------------
require('strict')
 
local DebugHelper = {}
local ScribuntoUnit = {}
 
-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = mw.loadData('Module:ScribuntoUnit/config')
 
-------------------------------------------------------------------------------
-- Concatenates keys and values, ideal for displaying a template or parser function argument table.
-- @param keySeparator glue between key and value (defaults to " = ")
-- @param separator glue between different key-value pairs (defaults to ", ")
Line 42 ⟶ 48:
local type1 = type(t1)
local type2 = type(t2)
 
if type1 ~= type2 then
return false
Line 49 ⟶ 55:
return t1 == t2
end
 
local metatable = getmetatable(t1)
if not ignoreMetatable and metatable and metatable.__eq then
return t1 == t2
end
 
for k1, v1 in pairs(t1) do
local v2 = t2[k1]
Line 66 ⟶ 72:
end
end
 
return true
end
Line 81 ⟶ 87:
level = (level or 1) + 1
details.trace = debug.traceback('', level)
details.source = mwstring.text.splitmatch(details.trace, '^%s*stack traceback:%s*(%S*: )')[5]
 
-- this would be more robust but does not work
-- local match = string.match(details.trace, '^%s*stack traceback:%s*(%S*): ')
-- details.source = match and match[1] or ''
-- setmetatable(details, {
-- __tostring: function() return details.text end
-- })
 
error(details, level)
end
Line 94 ⟶ 98:
-------------------------------------------------------------------------------
-- when used in a test, that test gets ignored, and the skipped count increases by one.
--
function ScribuntoUnit:markTestSkipped()
DebugHelper.raise({ScribuntoUnit = true, skipped = true}, 3)
Line 124 ⟶ 128:
-- @param plain search is made with a plain string instead of a ustring pattern
--
function ScribuntoUnit:assertContainsassertStringContains(pattern, s, plain, message)
if type(pattern) ~= 'string' then
DebugHelper.raise({
Line 140 ⟶ 144:
end
if not mw.ustring.find(s, pattern, nil, plain) then
local display = s
if #display > 100 then
display = mw.ustring.sub(display, 1, 90) .. " ... " .. mw.ustring.sub(display, -10, -1)
end
DebugHelper.raise({
ScribuntoUnit = true,
text = mw.ustring.format('Failed to find %s "%s" in string "%s"', plain and "plain string" or "pattern", pattern, displays),
message = message
}, 2)
end
end
 
-------------------------------------------------------------------------------
-- Checks an input string doesn't contain the expected string
-- @param message optional description of the test
-- @param plain search is made with a plain string instead of a ustring pattern
--
function ScribuntoUnit:assertNotStringContains(pattern, s, plain, message)
if type(pattern) ~= 'string' then
DebugHelper.raise({
ScribuntoUnit = true,
text = mw.ustring.format("Pattern type error (expected string, got %s)", type(pattern)),
message = message
}, 2)
end
if type(s) ~= 'string' then
DebugHelper.raise({
ScribuntoUnit = true,
text = mw.ustring.format("String type error (expected string, got %s)", type(s)),
message = message
}, 2)
end
local i, j = mw.ustring.find(s, pattern, nil, plain)
if i then
local match = mw.ustring.sub(s, i, j)
DebugHelper.raise({
ScribuntoUnit = true,
text = mw.ustring.format('Found match "%s" for %s "%s"', match, plain and "plain string" or "pattern", pattern),
message = message
}, 2)
Line 156 ⟶ 187:
-- @param message optional description of the test
-- @example assertEquals(4, add(2,2), "2+2 should be 4")
--
function ScribuntoUnit:assertEquals(expected, actual, message)
 
Line 216 ⟶ 247:
function ScribuntoUnit:assertDeepEquals(expected, actual, message)
if not DebugHelper.deepCompare(expected, actual) then
if type(expected) == 'table' then
expected = mw.dumpObject(expected)
end
if type(actual) == 'table' then
actual = mw.dumpObject(actual)
end
DebugHelper.raise({
ScribuntoUnit = true,
Line 261 ⟶ 298:
expected = processed2,
expectedRaw = text2,
message = message,
}, 2)
end
end
 
-------------------------------------------------------------------------------
-- Checks that a parser function gives the expected output.
-- @param message optional description of the test
-- @example assertParserFunctionEquals("Hello world", "msg:concat", {"Hello", " world"})
function ScribuntoUnit:assertParserFunctionEquals(expected, pfname, args, message)
local frame = self.frame
local actual = frame:callParserFunction{ name = pfname, args = args}
if expected ~= actual then
DebugHelper.raise({
ScribuntoUnit = true,
text = string.format("Failed to assert that %s with args %s equals expected %s after preprocessing",
DebugHelper.concatWithKeys(args), pfname, expected),
actual = actual,
actualRaw = pfname,
expected = expected,
message = message,
}, 2)
Line 269 ⟶ 326:
-- Checks that a template gives the expected output.
-- @param message optional description of the test
-- @example assertTemplateEquals("Hello world", "concat", {"Hello", " world"})
function ScribuntoUnit:assertTemplateEquals(expected, template, args, message)
local frame = self.frame
local actual = frame:expandTemplate({ title = template, args) = args}
if expected ~= actual then
DebugHelper.raise({
Line 282 ⟶ 339:
expected = expected,
message = message,
}, 2)
end
end
 
-------------------------------------------------------------------------------
-- Checks whether a function throws an error
-- @param fn the function to test
-- @param expectedMessage optional the expected error message
-- @param message optional description of the test
function ScribuntoUnit:assertThrows(fn, expectedMessage, message)
local succeeded, actualMessage = pcall(fn)
if succeeded then
DebugHelper.raise({
ScribuntoUnit = true,
text = 'Expected exception but none was thrown',
message = message,
}, 2)
end
-- For strings, strip the line number added to the error message
actualMessage = type(actualMessage) == 'string'
and string.match(actualMessage, 'Module:[^:]*:[0-9]*: (.*)')
or actualMessage
local messagesMatch = DebugHelper.deepCompare(expectedMessage, actualMessage)
if expectedMessage and not messagesMatch then
DebugHelper.raise({
ScribuntoUnit = true,
expected = expectedMessage,
actual = actualMessage,
text = string.format('Expected exception with message %s, but got message %s',
tostring(expectedMessage), tostring(actualMessage)
),
message = message
}, 2)
end
Line 292 ⟶ 381:
function ScribuntoUnit:new(o)
o = o or {}
meta =setmetatable(o, {__index = self})
setmetatable(o, meta)
o.run = function(frame) return self:run(o, frame) end
return o
Line 302 ⟶ 390:
--
function ScribuntoUnit:init(frame)
self.frame = frame or mw.getCurrentFrame()
self.successCount = 0
self.failureCount = 0
Line 315 ⟶ 403:
--
function ScribuntoUnit:runTest(suite, name, test)
local success, details = pcall(test, suite)
if success then
Line 333 ⟶ 421:
end
message = message .. details.text
table.insert(self.results, {name = name, error = true, message = message, expected = details.expected, actual = details.actual, testname = details.message})
end
end
Line 339 ⟶ 427:
-------------------------------------------------------------------------------
-- Runs all tests and displays the results.
--
function ScribuntoUnit:runSuite(suite, frame)
self:init(frame)
local names = {}
for name, func in pairs(suite) do
for name in pairs(suite) do
if name:find('^test') then
table.insert(names, name)
self:runTest(suite, name, func)
end
end
table.sort(names) -- Put tests in alphabetical order.
for i, name in ipairs(names) do
local func = suite[name]
self:runTest(suite, name, func)
end
return {
successCount = self.successCount,
Line 359 ⟶ 453:
-- Can be called without a frame, in which case it will use mw.log for output
-- @param displayMode see displayResults()
--
function ScribuntoUnit:run(suite, frame)
frame = frame or mw.getCurrentFrame()
local testData = self:runSuite(suite, frame)
if frame and frame.args then
return self:displayResults(testData, frame.args.displayMode or 'table')
else
Line 400 ⟶ 493:
end
end
 
-- TODO l10n
 
function ScribuntoUnit:displayResultsAsShort(testData)
local text = string.format('success: %d, error: %d, skipped: %d'cfg.shortResultsFormat, testData.successCount, testData.failureCount, testData.skipCount)
if testData.failureCount > 0 then
text = '<span class="error">' .. text .. '</span>'
Line 412 ⟶ 503:
 
function ScribuntoUnit:displayResultsAsTable(testData)
local successIcon, failIcon = self.frame:preprocess('{{tick}}'cfg.successIndicator), self.frame:preprocess('{{cross}}'cfg.failureIndicator)
local text = '{| class="wikitable scribunto-test-table"\n'
if testData.failureCount > 0 then
text = text .. '! !! Name !! Expected !! Actual\n'
local msg = mw.message.newRawMessage(cfg.failureSummary, testData.failureCount):plain()
msg = self.frame:preprocess(msg)
if cfg.failureCategory then
msg = cfg.failureCategory .. msg
end
text = text .. failIcon .. ' ' .. msg .. '\n'
else
text = text .. successIcon .. ' ' .. cfg.successSummary .. '\n'
end
text = text .. '{| class="wikitable scribunto-test-table"\n'
text = text .. '!\n! ' .. cfg.nameString .. '\n! ' .. cfg.expectedString .. '\n! ' .. cfg.actualString .. '\n'
for _, result in ipairs(testData.results) do
text = text .. '|-\n'
if result.error then
text = text .. '| ' .. failIcon .. ' || ' .. result.name .. ' |\n| '
if (result.expected and result.actual) then
local textname = text .. tostring(result.expected) .. ' || ' .. tostring(result.actual) .. '\n'name
if result.testname then
name = name .. ' / ' .. result.testname
end
text = text .. name .. '\n| ' .. mw.text.nowiki(tostring(result.expected)) .. '\n| ' .. mw.text.nowiki(tostring(result.actual)) .. '\n'
else
text = text .. result.name .. '\n| ' .. ' colspan="2" | ' .. mw.text.nowiki(result.message) .. '\n'
end
else
text = text .. '| ' .. successIcon .. ' |\n| ' .. result.name .. ' || \n|\n|\n'
end
end