Modul:Test

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('&', '&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.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.

  1. 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:
  2. 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.
  3. 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.
  4. 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.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.