Modul:Test
Zur Navigation springen
Zur Suche springen
Vorlagenprogrammierung | Diskussionen | Lua | Unterseiten | |||
Modul | Deutsch | English
|
Modul: | Dokumentation |
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus
--[=[ Test 2022-12-10
test a module
Autor: Vollbracht
* test.single(frame)
{{#invoke:Test|single |module=<module name> |expected=<html or wikitext>
|func=<function to be tested>
|<key=value> |<key=value> ... }}
{{#invoke:Test|single |module=<module name> |expected=<html or wikitext>
|func=<function to be tested>
|unnamed=<value>{!}<value>{!} ... }}
{{#invoke:Test|delimitLogs}}
(generates horrizontal bar in log)
]=]
local p = {}
local USAGE =
[[<code>{{#invoke:Test|single|module=<module name> |expected=<html or wikitext>
|func=<function to be tested> |<key=value> |<key=value> ... }}</code>]]
--[[
Call
an object representating whatever is necessary to call a function
constructors:
new(frame, action, data) testcase
suggester(frame, test, result) an invoke call of a Test function
fields:
frame object copy of the calling objects frame
wikitext string available for invoke only
luatext string
modulename string
moduleprefix string 'Module' or 'Modul'
Module required module by this.moduleprefix:this.modulename
functionpath array of strings
parameters array
methods:
toString() returns <nowiki>-wraped wikitext or (if inavailable)
unwraped luatext with its particular parameters
toCode() returns <code>-wraped toString()
]]
local Call = {
properties = {module=1, func=2, expected=4, unnamed=8, export=16},
toString = function(this) -- ####
if this.wikitext then
return this.frame:extensionTag('nowiki', this.wikitext, {})
end
if this.wraper then return this.wraper end
return this.luatext
end,
toCode = function(this)
return this.frame:extensionTag('code', this:toString(), {})
end,
}
--[[
new(frame, action, data)
constructor for a client call, i. e. a test case
parameters:
frame environmental frame of the test providing call
action string representing the module and its directly exported
function, which is to be tested
possible formats:
"<module>/<function path>"
{modulename=<module name>, functionpath=<function path>}
{ moduleprefix=<module prefix>, modulename=<module name>,
functionpath=<function path> }
Call
data all parameters remaining in test case, which are handed over to
the client module and function that is to be tested
returns the new call object
]]
function Call:new(frame, action, data)
if not frame or not frame.extensionTag or not action then return nil end
local result = {
frame = frame,
parameters = data
}
local set, success, extendSearch = false
if type(action) == 'string' then
result.Module = nil
result.functionpath = {}
set = {action}
if not action:find('^Modul') then extendSearch = true end
while not result.Module do
set = {set[1]:match('(.*)%/(%a%w*)$')}
if not set[1] then return nil end
result.functionpath = {set[2], unpack(result.functionpath)}
success, result.Module = pcall(require, set[1])
if success then
if extendSearch then
result.moduleprefix = ''
result.modulename = set[1]
else
result.moduleprefix, result.modulename
= set[1]:match('(Modul[e]?%:)(.+)')
end
elseif extendSearch then
success, result.Module = pcall(require, 'Modul:' .. set[1])
if result.Module then
result.modulename = set[1]
result.moduleprefix = 'Modul:'
else
success, result.Module = pcall(require, 'Module:' .. set[1])
if result.Module then
result.modulename = set[1]
result.moduleprefix = 'Module:'
end
end
else return nil
end
end
elseif type(action) == 'table' then
if not action.modulename then return nil end
if not action.functionpath then return nil end
result.modulename = action.modulename
result.functionpath = functionpath
if action.Module then
result.Module = action.Module
if action.moduleprefix then
result.moduleprefix = action.moduleprefix
end
else
if action.moduleprefix then
result.moduleprefix = action.moduleprefix
success, result.Module = pcall(require,
result.moduleprefix .. result.modulename)
if not success then return nil end
else
success, result.Module = pcall(require, result.modulename)
if not success then
local mn = 'Modul:' .. result.modulename
success, result.Module = pcall(require, mn)
if success then result.moduleprefix = 'Modul:'
else
local mn = 'Module:' .. result.modulename
success, result.Module = pcall(require, mn)
if success then result.moduleprefix = 'Module:'
else return nil end
end
end
end
end
end
local luadata = ''
local wtData = ''
for i, v in ipairs(data) do
luadata = luadata .. ', [' .. i .. '] = "' .. v .. '"'
wtData = wtData .. '|' .. v
end
for k, v in pairs(data) do
luadata = luadata .. ', ' .. k .. ' = "' .. v .. '"'
wtData = wtData .. '|' .. k .. '=' .. v .. ' '
end
if luadata == '' then
result.luatext = table.concat(result.functionpath, '/') .. '()'
if #result.functionpath == 1 then
result.wikitext = '{{#invoke:' .. result.modulename .. ' |'
.. result.functionpath[1] .. '}}'
end
else
result.luatext = table.concat(result.functionpath, '/') .. '{'
.. luadata:sub(3) .. '}'
if #result.functionpath == 1 then
result.wikitext = '{{#invoke:' .. result.modulename .. ' |'
.. result.functionpath[1]
.. wtData:gsub('%s*$', '') .. '}}'
end
end
setmetatable(result, self)
self.__index = self
return result
end
--[[
suggester(frame, test, suggestion)
constructor for a test call
parameters:
frame environmental frame of the test providing call
test name of the used test function in Test module
suggestion suggested result for the tested function
returns the new call object
]]
function Call:suggester(frame, test, suggestion)
local result = {
frame = frame,
modulename = 'Test',
functionpath = { test },
parameters = frame.args
}
local luadata = ''
local wtData = ''
for i, v in ipairs(frame.args) do
luadata = luadata .. ', [' .. i .. '] = "' .. v .. '"'
wtData = wtData .. '|' .. v
end
for k, v in pairs(frame.args) do
if not k:find('^%d+$') then
luadata = luadata .. ', ' .. k .. ' = "' .. v .. '"'
wtData = wtData .. '|' .. k .. '=' .. v .. ' '
end
end
luadata = luadata .. ', expected = "' .. suggestion .. '"'
wtData = wtData .. '|expected=' .. suggestion
result.luatext = test .. '{' .. luadata:sub(3) .. '}'
result.wikitext = '{{#invoke:Test |' .. test .. ' ' .. wtData .. ' }}'
setmetatable(result, self)
self.__index = self
return result
end
--[[
Counter
Object: value set of prefix, number and postfix with an optional key name
]]
local Counter = {
use = function(this)
local result = this.prefix .. tostring(this.value) .. this.postfix
this.value = this.value + 1
return result
end,
keyValue = function(this, index)
return index + 1, this.prefix .. index .. this.postfix
end
}
function Counter:new(source, key)
if not source then return nil end
local result = {}
if key then result.key = key end
if type(source) == 'string' then
result.prefix, result.value, result.postfix
= source:match('(.*)%<counter>(%d*)%<%/counter%>(.*)')
if result.value then
if result.value == '' then result.value = 0
else result.value = tonumber(result.value) end
else
result.prefix, result.postfix
= source:match('(.*)%<counter%s?%/%>(.*)')
if not result.prefix then return nil end
result.value = 0
end
end
if type(source) == 'table' then result = source end
if not result.prefix or not result.value or not result.postfix then
return nil
end
setmetatable(result, self)
self.__index = self
return result
end
--[[
Client
a test type object
constructor:
new(frame)
fields:
call a Call object
expected string for comparison
actual string or an object with a __tostring method
( Following fields are for internal use only, because they aren't
balanced. Use get<Field>() instead! )
accordance1 wikitext - do not get
accordance2 wikitext - do not get
missedMark wikitext - get includes accordances
variation wikitext - get includes accordances
methods:
getVariation() colored wikitext output of variation
including accordances
getMissedMark() colored wikitext output of missedMark
including accordances
]]
local Client = {
getVariation = function(this)
if not this.variation then return '' end
local result = this.accordance1
if result then
if result ~= '' then
result = this.call.frame:extensionTag('span',
this.call.frame:extensionTag('nowiki', result, {}),
{ style="background-color:#bfa;" })
end
else result = '' end
result = result
.. this.call.frame:extensionTag('span',
this.call.frame:extensionTag('nowiki', this.variation, {}),
{ style="background-color:#fba;" })
if not this.accordance2 or this.accordance2 == '' then
return this.call.frame:extensionTag('code', result, {}) end
return this.call.frame:extensionTag('code', result
.. this.call.frame:extensionTag('span',
this.call.frame:extensionTag( 'nowiki', this.accordance2,
{}),
{ style="background-color:#bfa;" }), {})
end,
getMissedMark = function(this)
if not this.missedMark or this.missedMark == '' then
return this.call.frame:extensionTag('code',
this.call.frame:extensionTag('span',
this.call.frame:extensionTag('nowiki', this.expected, {}),
{ style="background-color:#bfa;" }), {})
end
local result = this.accordance1
if result then
if result ~= '' then
result = this.call.frame:extensionTag('span',
this.call.frame:extensionTag('nowiki', result, {}),
{ style="background-color:#bfa;" })
end
else result = '' end
result = result
.. this.call.frame:extensionTag('nowiki', this.missedMark, {})
if not this.accordance2 or this.accordance2 == '' then
return this.call.frame:extensionTag('code', result, {}) end
return this.call.frame:extensionTag('code', result
.. this.call.frame:extensionTag('span',
this.call.frame:extensionTag( 'nowiki', this.accordance2,
{}),
{ style="background-color:#bfa;" }), {})
end,
toString = function(this) -- ####
if this.variation then return this:getVariation() end
return this.actual
end,
reducedRows = function(this, test, processCall)
local result = '<tr><td>'
if processCall then
result = this.call:toString() .. '</td></tr><tr><td>'
end
if this.expected then
return result .. this:toString() .. '</td></tr><tr><td>'
.. this:getMissedMark() .. '</td></tr>'
end
if test then
local call = Call:suggester(this.call.frame, test, this.actual)
return result
.. this.call.frame:extensionTag( 'syntaxhighlight',
call.wikitext, {
lang="html+handlebars"
})
.. '</td></tr>'
end
return result
.. this.call.frame:extensionTag('nowiki', this.actual, {})
.. '</td></tr>'
end
}
function Client:new(frame)
local result = {}
local data = {}
if #frame.args > 0 then data = {unpack(frame.args)} end
local action = nil
-- 1.: unwrap action and expectation from frame.args
for k, v in pairs(frame.args) do
if k == 'action' then
-- extract 1st action only; actions may be delimited by '//'
v = v:gsub('^%/*', '')
local i = v:find('//')
if i then
action = v:sub(1, i-1)
if #v > i+2 then v = v:sub(i+2)
else v = nil end
else
action = v
v = nil
end
elseif k == 'expected' then
-- extract 1st expectation only;
-- expectations may be delimited by '<split></split>'
local i, j = v:find('.%<split%>%<%/split%>.')
if i then
result.expected = v:sub(1, i - 1)
if #v > j+1 then v = v:sub(j + 1)
else v = nil end
else
result.expected = v
v = nil
end
else
local s, c = v:gsub('%<inTestSeries%s*%>?%<?%/%>?', '')
if c > 0 then
s = s:gsub('inTestSeries%>', '')
if not result.testSeries then result.testSeries = {} end
table.insert(result.testSeries, k)
v = s
elseif v:find('%<counter') then
if not result.counters then result.counters = {} end
local c = Counter:new(v, k)
if c then
table.insert(result.counters, c)
v = c:use()
end
end
end
if v then data[k] = v end
end
if not action then
return {
Error = 'Missing argument "action=<module name>/<function name>".'
}
end
-- 2.: generate string representation from action
result.call = Call:new(frame, action, data)
local e = ''
if not result.call then
e = 'Inappropriate argument "action" is "' .. action
.. '" but should be "<module name>/<function name>" instead.'
return { Error = e }
end
mw.logObject(result, 'result')
-- 3.: retreive executable function from action
local Function = result.call.Module
if not Function then
e = 'Inappropriate module name "' .. result.call.modulename
.. '" in test call.'
if not result.call.moduleprefix then
if result.call.wikitext then
e = e .. ' (Even with "Module:" or "Modul:" prefix!)'
else
e = e .. ' Try providing a "Module:" or "Modul:" prefix!'
end
end
return { Error = e }
end
for _, v in ipairs(result.call.functionpath) do
Function = Function[v]
if not Function then
e = 'Inavailable export "' .. v
.. '" in ' .. result.call.luatext .. ' call.'
return { Error = e }
end
end
-- 4.: get actual result of execution of this function
local handler = function(err)
e = err
mw.logObject(e, 'error in execution of ' .. result.call.luatext)
end
local success = false
if result.call.wikitext then
mw.logObject(result, 'try wikitext')
local c = result.call
local f = frame:newChild({ title = c.modulename, args=c.parameters })
fkt = function()
return Function(f)
end
success, result.actual = xpcall(fkt, handler, {})
end
if not success then
mw.logObject(result, 'try without wikitext')
success, result.actual = xpcall(Function, handler, unpack(data))
end
if not success or e and e ~= '' then return {Error = e} end
-- 5.: tostring actual result
if type(result.actual) == 'table' and result.actual.toString then
result.actual = result.actual:toString()
elseif type(result.actual) ~= 'string' then
local _, ra = pcall(tostring, result.actual)
if ra then result.actual = ra
else result.actual = mw.dumpObject(result.actual) end
end
-- 6.: get dif
mw.logObject(result, 'client at get dif')
if not result.expected then
elseif result.actual == result.expected then
result.accordance1 = result.actual
else
local aList = mw.text.split(result.actual, '')
local eList = mw.text.split(result.expected, '')
local i = 1
result.accordance1 = ""
while i <= #aList and i <= #eList and aList[i] == eList[i] do
result.accordance1 = result.accordance1 .. aList[i]
i = i + 1
end
result.accordance1 = result.accordance1:gsub('&', '&')
local ia = #aList
local ie = #eList
result.accordance2 = ""
while i <= ia and i <= ie and aList[ia] == eList[ie] do
result.accordance2 = aList[ia] .. result.accordance2
ia = ia - 1
ie = ie - 1
end
result.accordance2 = result.accordance2:gsub('&', '&')
if i > ia then
result.variation = '[..]'
else
result.variation = ''
while i <= ia do
result.variation = aList[ia] .. result.variation
ia = ia - 1
end
end
result.variation = result.variation:gsub('&', '&')
result.missedMark = ''
while i <= ie do
result.missedMark = eList[ie] .. result.missedMark
ie = ie - 1
end
result.missedMark = result.missedMark:gsub('&', '&')
end
setmetatable(result, self)
self.__index = self
return result
end
p.single = function(frame)
if not frame.args then
return 'First usage: ' .. frame:extensionTag('syntaxhighlight',
'{{#invoke:Test|single|action=<module name>/<exported function>|...'
.. '}}', { lang="html+handlebars"}) .. ' with whatever params your fun'
.. 'ction might need in addition'
end
local client = Client:new(frame)
if client.Error then return client.Error end
local result = '<table class="wikitable"><tr><th>call</th><td>'
.. frame:extensionTag('code', client.call:toString(), {})
.. '</td></tr><tr><th>actual value</th><td>'
.. client.actual .. '</td></tr>'
if not client.expected then
local call = Call:suggester(frame, 'single', client.actual)
return result .. '<tr><td colspan="2">following call whould indica'
.. 'te success:</td></tr><tr><td colspan="2">'
.. frame:extensionTag('syntaxhighlight', call.wikitext, {
lang="html+handlebars"
}) .. '</td></tr></table>'
end
if client.variation then
return result .. '<tr><th>expected value></th><td>' .. client.expected
.. '</td></tr><tr><td colspan="2">' .. client:getVariation()
.. '</td></tr><tr><td colspan="2">'
.. client:getMissedMark() .. '</td></tr></table>'
end
return result .. '</table>'
end
p.testRows = function(frame)
if not frame.args then
return 'First usage: ' .. frame:extensionTag('syntaxhighlight',
[[{|
{{#invoke:Test|testRows|action=<module name>/<exported function>|...}}
|}]], { lang="html+handlebars"})
.. ' with whatever params your function might need in addition'
end
local client = Client:new(frame)
if client.Error then return '<tr><td>' .. client.Error .. '</td></tr>' end
return client:reducedRows('testRows')
end
p.testTable = function(frame)
end
p.delimitLogs = function()
mw.log("___________________________ next test ___________________________")
end
return p