Zum Inhalt springen

Modul:Test

aus Wikipedia, der freien Enzyklopädie
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 26. Dezember 2022 um 06:02 Uhr durch Vollbracht (Diskussion | Beiträge). Sie kann sich erheblich von der aktuellen Version unterscheiden.
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

--[[
	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
}

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
	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('&', '&amp;')
		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('&', '&amp;')
		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('&', '&amp;')
		result.missedMark = ''
		while i <= ie do
			result.missedMark = eList[ie] .. result.missedMark
			ie = ie - 1
		end
		result.missedMark = result.missedMark:gsub('&', '&amp;')
	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.testTable = function(frame)
	
end

return p