Modul:Test
Modul:Test
| 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
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.