Modul:Test
Erscheinungsbild
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>{!} ... }}
]=]
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) an invoke call of a Test function
invoker(frame, action, data) testcase callable by invoke
requirer(frame, action, data) testcase callable by require
suggester(frame, test, result) an invoke call of a Test function
fields:
wikitext string available for invoke only
luatext string
modulename string
moduleprefix string 'Module' or 'Modul'
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()
getModule() returns required module by this name
extensionTag(element, core, attributes) copy of frames extensionTag
]]
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,
getModule = function(this)
local name = this.modulename
if not name or name == '' then return nil end
if this.wikitext and
(not this.moduleprefix or this.moduleprefix == '') then
local _, result = pcall(require, 'Module:' .. name)
if not result then _, result = pcall(require, 'Modul:' .. name) end
return result
end
if not this.moduleprefix or this.moduleprefix == '' then return nil end
local _, result = pcall(require, this.moduleprefix .. ':' .. name)
return result
end,
toCode = function(this)
return this.frame:extensionTag('code', this:toString(), {})
end,
}
--[[
invoker(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
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:invoker(frame, action, data)
if not frame or not frame.extensionTag or not action then return nil end
if type(action) == 'string' then
action = mw.text.split(action, '$s*%/%s*')
end
if type(action) ~= 'table' or #action < 2 then return nil end
local result = {
frame = frame,
functionpath = { action[2] },
parameters = data
}
result.moduleprefix, result.modulename = action[1]:match('(Modul[e]?):(.*)')
if not result.modulename then result.modulename = action[1] 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 = action[2] .. '()'
result.wikitext = '{{#invoke:' .. result.modulename .. ' |'
.. action[2] .. '}}'
else
result.luatext = action[2] .. '{' .. luadata:sub(3) .. '}'
result.wikitext = '{{#invoke:' .. result.modulename .. ' |'
.. action[2] .. wtData:gsub('%s*$', '') .. '}}'
end
setmetatable(result, self)
self.__index = self
return result
end
--[[
requirer(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
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:requirer(frame, action, data)
if not frame or not frame.extensionTag or not action then return nil end
if type(action) == 'string' then
action = mw.text.split(action, '$s*%/%s*')
end
if type(action) ~= 'table' or #action < 2 then return nil end
local result = {
frame = frame,
functionpath = {},
parameters = data,
wikitext = nil
}
for i = 2, #action do table.insert(result.functionpath, action[i]) end
result.moduleprefix, result.modulename = action[1]:match('(Modul[e]?):(.*)')
if not result.modulename then result.modulename = action[1] end
local luadata = ''
for i, v in ipairs(data) do
luadata = luadata .. ', [' .. i .. '] = "' .. v .. '"'
end
for k, v in ipairs(data) do
luadata = luadata .. ', ' .. k .. ' = "' .. v .. '"'
end
if luadata == '' then
result.luatext = action[2] .. '()'
else
result.luatext = action[2] .. '{' .. luadata:sub(3) .. '}'
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
luadata = luadata .. ', ' .. k .. ' = "' .. v .. '"'
wtData = wtData .. '|' .. k .. '=' .. v .. ' '
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
--[[
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
}
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
end
if v then data[k] = v end
end
if not action then
return {
Error = 'Missing argument "action=<module name>/<function name>".'
}
end
action = mw.text.split(action, '%/')
-- 2.: generate string representation from action
local e = ""
if #action > 2 then
result.call = Call:requirer(frame, action, data)
elseif #action == 2 then
result.call = Call:invoker(frame, action, data)
else
e = 'Inappropriate argument "action" is "' .. action[1]
.. '" 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:getModule()
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
handler = function(err)
e = err
mw.logObject(e, 'tostring error in result of '
.. action[#action] .. ' function')
end
fkt = function()
return tostring(result.actual)
end
success, result.actual = xpcall(fkt, handler, {})
if not success or e and e ~= '' then return {Error = e} 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, 'single2', 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
return p