Modulo:ScribuntoUnit: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
Moroboshi (discussione | contributi)
Nuova pagina: ------------------------------------------------------------------------------- -- Unit tests for Scribunto. -----------------------------------------------------------...
 
Update from master using #Synchronizer
 
(2 versioni intermedie di 2 utenti non mostrate)
Riga 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 ", ")
Riga 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
Riga 97 ⟶ 101:
function ScribuntoUnit:markTestSkipped()
DebugHelper.raise({ScribuntoUnit = true, skipped = true}, 3)
end
 
-------------------------------------------------------------------------------
-- Unconditionally fail a test
-- @param message optional description of the test
--
function ScribuntoUnit:fail(message)
DebugHelper.raise({ScribuntoUnit = true, text = "Test failed", message = message}, 2)
end
 
Riga 185 ⟶ 197:
--
function ScribuntoUnit:assertEquals(expected, actual, message)
 
if type(expected) == 'number' and type(actual) == 'number' then
self:assertWithinDelta(expected, actual, 1e-8, message)
 
elseif expected ~= actual then
DebugHelper.raise({
Riga 198 ⟶ 208:
}, 2)
end
end
 
-------------------------------------------------------------------------------
-- Checks that an input does not have the expected value.
-- @param message optional description of the test
-- @example assertNotEquals(5, add(2,2), "2+2 should not be 5")
--
function ScribuntoUnit:assertNotEquals(expected, actual, message)
if type(expected) == 'number' and type(actual) == 'number' then
self:assertNotWithinDelta(expected, actual, 1e-8, message)
elseif expected == actual then
DebugHelper.raise({
ScribuntoUnit = true,
text = string.format("Failed to assert that %s does not equal expected %s", tostring(actual), tostring(expected)),
actual = actual,
expected = expected,
message = message,
}, 2)
end
end
 
-------------------------------------------------------------------------------
-- Validates that both the expected and actual values are numbers
-- Checks that 'actual' is within 'delta' of 'expected'.
-- @param message optional description of the test
--
-- @example assertEquals(1/3, 9/3, "9/3 should be 1/3", 0.000001)
local function ScribuntoUnit:assertWithinDeltavalidateNumbers(expected, actual, delta, message)
if type(expected) ~= "number" then
DebugHelper.raise({
Riga 213 ⟶ 241:
expected = expected,
message = message,
}, 23)
end
if type(actual) ~= "number" then
Riga 222 ⟶ 250:
expected = expected,
message = message,
}, 23)
end
end
 
-------------------------------------------------------------------------------
-- Checks that 'actual' is within 'delta' of 'expected'.
-- @param message optional description of the test
-- @example assertWithinDelta(1/3, 3/9, 0.000001, "3/9 should be 1/3")
function ScribuntoUnit:assertWithinDelta(expected, actual, delta, message)
validateNumbers(expected, actual, message)
local diff = expected - actual
if diff < 0 then diff = - diff end -- instead of importing math.abs
Riga 230 ⟶ 266:
ScribuntoUnit = true,
text = string.format("Failed to assert that %f is within %f of expected %f", actual, delta, expected),
actual = actual,
expected = expected,
message = message,
}, 2)
end
end
 
-------------------------------------------------------------------------------
-- Checks that 'actual' is not within 'delta' of 'expected'.
-- @param message optional description of the test
-- @example assertNotWithinDelta(1/3, 2/3, 0.000001, "1/3 should not be 2/3")
function ScribuntoUnit:assertNotWithinDelta(expected, actual, delta, message)
validateNumbers(expected, actual, message)
local diff = expected - actual
if diff < 0 then diff = - diff end -- instead of importing math.abs
if diff <= delta then
DebugHelper.raise({
ScribuntoUnit = true,
text = string.format("Failed to assert that %f is not within %f of expected %f", actual, delta, expected),
actual = actual,
expected = expected,
Riga 294 ⟶ 349:
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)
Riga 302 ⟶ 377:
-- 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({
Riga 317 ⟶ 392:
}, 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
end
 
-------------------------------------------------------------------------------
-- Checks whether a function doesn't throw an error
-- @param fn the function to test
-- @param message optional description of the test
function ScribuntoUnit:assertDoesNotThrow(fn, message)
local succeeded, actualMessage = pcall(fn)
if succeeded then
return
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
DebugHelper.raise({
ScribuntoUnit = true,
actual = actualMessage,
text = string.format('Expected no exception, but got exception with message %s',
tostring(actualMessage)
),
message = message
}, 2)
end
 
Riga 325 ⟶ 455:
function ScribuntoUnit:new(o)
o = o or {}
setmetatable(o, {__index._tests = self{})
setmetatable(o, {
__index = self,
__newindex = function (t, k, v)
if type(k) == "string" and k:find('^test') and type(v) == "function" then
-- Store test functions in the order they were defined
table.insert(o._tests, {name = k, test = v})
else
rawset(t, k, v)
end
end
})
o.run = function(frame) return self:run(o, frame) end
return o
Riga 334 ⟶ 475:
--
function ScribuntoUnit:init(frame)
self.frame = frame or mw.getCurrentFrame()
self.successCount = 0
self.failureCount = 0
Riga 360 ⟶ 501:
else
self.failureCount = self.failureCount + 1
local message = details.source or ""
if details.message then
message = message .. details.message .. "\n"
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
Riga 374 ⟶ 515:
function ScribuntoUnit:runSuite(suite, frame)
self:init(frame)
for i, testDetails in ipairs(suite._tests) do
local names = {}
self:runTest(suite, testDetails.name, testDetails.test)
for name in pairs(suite) do
if name:find('^test') then
table.insert(names, name)
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 {
Riga 400 ⟶ 533:
function ScribuntoUnit:run(suite, frame)
local testData = self:runSuite(suite, frame)
if frame and frame.args then
return self:displayResults(testData, frame.args.displayMode or 'table')
else
Riga 437 ⟶ 570:
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>'
Riga 449 ⟶ 580:
 
function ScribuntoUnit:displayResultsAsTable(testData)
local successIcon, failIcon = self.frame:preprocess('{{tick}}'cfg.successIndicator), self.frame:preprocess('{{cross}}'cfg.failureIndicator)
local text = ''
if testData.failureCount > 0 then
local msg = mw.message.newRawMessage(cfg.failureSummary, testData.failureCount):plain()
local msg = "'''$1 {{PLURAL:$1|test|tests}} failed'''."
msg = mw.message.newRawMessage(msg, 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'
local msg = "'''All tests passed'''."
text = text .. successIcon .. ' ' .. msg .. '\n'
end
text = text .. '{| class="wikitable scribunto-test-table"\n'
text = text .. '!\n! Name' .. cfg.nameString .. '\n! Expected' .. cfg.expectedString .. '\n! Actual' .. cfg.actualString .. '\n'
for _, result in ipairs(testData.results) do
text = text .. '|-\n'
if result.error then
text = text .. '| ' .. failIcon .. '\n| ' .. result.name .. '\n| '
if (result.expected and result.actual) then
local name = result.name
text = text .. mw.text.nowiki(tostring(result.expected)) .. '\n| ' .. mw.text.nowiki(tostring(result.actual)) .. '\n'
if result.testname then
name = name .. ' / ' .. result.testname
end
text = text .. mw.text.nowiki(name) .. '\n| ' .. mw.text.nowiki(tostring(result.expected)) .. '\n| ' .. mw.text.nowiki(tostring(result.actual)) .. '\n'
else
text = text .. mw.text.nowiki(result.name) .. '\n| ' .. ' colspan="2" | ' .. mw.text.nowiki(result.message) .. '\n'
end
else
text = text .. '| ' .. successIcon .. '\n| ' .. mw.text.nowiki(result.name) .. '\n|\n|\n'
end
end