Bước tới nội dung

Mô đun:Convert/tester

Bách khoa toàn thư mở Wikipedia
-- Test the output from a template by comparing it with fixed text.-- The expected text must be in a single line, but can include-- "\n" (two characters) to indicate that a newline is expected.-- Tests are run (or created) by setting p.tests (string or table), or-- by setting page=PAGE_TITLE (and optionally section=SECTION_TITLE),-- then executing run_tests (or make_tests).local function collection()	-- Return a table to hold lines of text.	return {		n = 0,		add = function (self, s)			self.n = self.n + 1			self[self.n] = s		end,		join = function (self, sep)			return table.concat(self, sep or '\n')		end,	}endlocal function empty(text)	-- Return true if text is nil or empty (assuming a string).	return text == nil or text == ''endlocal function strip(text)	-- Return text with no leading/trailing whitespace.	return text:match("^%s*(.-)%s*$")endlocal function status_box(stats, expected, actual, iscomment)	local label, bgcolor, align, isfail	if iscomment then		actual = ''		align = 'center'		bgcolor = 'silver'		label = 'Cmnt'	elseif expected == '' then		stats.ignored = stats.ignored + 1		return actual, ''	elseif expected == actual then		stats.pass = stats.pass + 1		actual = ''		align = 'center'		bgcolor = 'green'		label = 'Được'	else		stats.fail = stats.fail + 1		align = 'center'		bgcolor = 'red'		label = 'Hỏng'		isfail = true	end	return actual, 'style="text-align:' .. align .. ';color:white;background:' .. bgcolor .. ';" | ' .. label, isfailendlocal function status_text(stats)	local bgcolor, ignored_text, msg	if stats.fail == 0 then		if stats.pass == 0 then			bgcolor = 'salmon'			msg = 'Không kiểm thử trường hợp nào'		else			bgcolor = 'green'			msg = string.format('Tất cả %d trường hợp qua được', stats.pass)		end	else		bgcolor = 'darkred'		msg = string.format('%d trường hợp thị thất bại', stats.fail)	end	if stats.ignored == 0 then		ignored_text = ''	else		bgcolor = 'salmon'		ignored_text = string.format(', đã bỏ qua %d trường hợp vì không có văn bản mong đợi', stats.ignored)	end	return '<span style="font-size:120%;color:white;background-color:' .. bgcolor .. ';">' .. msg .. ignored_text .. '.</span>'endlocal function run_template(frame, template, forcename, collapse_multiline)	-- Template "{{ example |  2  =  def  |  abc  |  name  =  ghi jkl  }}"	-- gives args { "  abc  ", "def", name = "ghi jkl" }.	if template:sub(1, 2) == '{{' and template:sub(-2, -1) == '}}' then		template = template:sub(3, -3) .. '|'  -- append sentinel to get last field	else		return '(invalid template)'	end	local args = {}	local index = 1	local templatename	for field in template:gmatch('(.-)|') do		if templatename == nil then			templatename = forcename or strip(field)			if templatename == '' then				return '(invalid template)'			end		else			local k, eq, v = field:match("^(.-)(=)(.*)$")			if eq then				k, v = strip(k), strip(v)  -- k and/or v can be empty				local i = tonumber(k)				if i and i > 0 and string.match(k, '^%d+$') then					args[i] = v				else					args[k] = v				end			else				while args[index] ~= nil do					-- Skip any explicit numbered parameters like "|5=five".					index = index + 1				end				args[index] = field			end		end	end	local function expand(t)		return frame:expandTemplate(t)	end	local ok, result = pcall(expand, { title = templatename, args = args })	if not ok then		result = 'Error: ' .. result	end	if collapse_multiline then		result = result:gsub('\n', '\\n')	end	return resultendlocal function _make_tests(frame, all_tests, forcename)	local maxlen = 38	for _, item in ipairs(all_tests) do		local template = item[1]		if template then			local templen = mw.ustring.len(template)			item.templen = templen			if maxlen < templen and templen <= 70 then				maxlen = templen			end		end	end	local result = collection()	for _, item in ipairs(all_tests) do		local template = item[1]		if template then			local actual = run_template(frame, template, forcename, true)			local pad = string.rep(' ', maxlen - item.templen) .. '  '			result:add(template .. pad .. actual)		else			local text = item.text			if text then				result:add(text)			end		end	end	-- Pre tags returned by a module are html tags, not like wikitext <pre>...</pre>.	return '<pre>\n' .. mw.text.nowiki(result:join()) .. '\n</pre>\n'endlocal function _run_tests(frame, all_tests, forcename)	local function safe_cell(text, multiline)		-- For testing {{convert}}, want wikitext like '[[kilogram|kg]]' to be unchanged		-- so the link works and so the displayed text is short (just "kg" in example).		text = text:gsub('(%[%[[^%[%]]-)|(.-%]%])', '%1\0%2')  -- replace pipe in piped link with a zero byte		text = text:gsub('{', '&#123;'):gsub('|', '&#124;')    -- escape '{' and '|'		text = text:gsub('%z', '|')                            -- restore pipe in piped link		if multiline then			text = text:gsub('\\n', '<br />')		end		return text	end	local function nowiki_cell(text, multiline)		text = mw.text.nowiki(text)		if multiline then			text = text:gsub('\\n', '<br />')		end		return text	end	local stats = { pass = 0, fail = 0, ignored = 0 }	local result = collection()	result:add('{| class="wikitable"')	result:add('! Bản mẫu !! Mong đợi !! Thực tế, nếu khác !! Kết quả')	for _, item in ipairs(all_tests) do		local template, expected = item[1], item[2] or ''		if template then			local actual = run_template(frame, template, forcename, true)			local sbox, isfail			actual, sbox, isfail = status_box(stats, expected, actual)			result:add('|-')			result:add('| ' .. safe_cell(template))			result:add('| ' .. safe_cell(expected, true))			result:add('| ' .. safe_cell(actual, true))			result:add('| ' .. sbox)			if isfail then				result:add('|-')				result:add('| align="center"| (above, nowiki)')				result:add('| ' .. nowiki_cell(expected, true))				result:add('| ' .. nowiki_cell(actual, true))				result:add('|')			end		else			local text = item.text			if text and text:sub(1, 3) == '---' then				actual, sbox, isfail = status_box(stats, '', '', true)				result:add('|-')				result:add('| colspan="3" style="color:white;background:silver;" | ' .. safe_cell(strip(text:sub(4)), true))				result:add('| ' .. sbox)			end		end	end	result:add('|}')	return status_text(stats) .. '\n\n' .. result:join()endlocal function get_page_content(page_title)	local t = mw.title.new(page_title)	if t then		local content = t:getContent()		if content then			if content:sub(-1) ~= '\n' then				content = content .. '\n'			end			return content		end	end	error('Could not read wikitext from "[[' .. page_title .. ']]".', 0)endlocal function _compare(frame, page_pairs)	local function diff_link(title1, title2)		return '<span class="plainlinks">[' ..			tostring(mw.uri.fullUrl('Special:ComparePages',				{ page1 = title1, page2 = title2 })) ..			' diff]</span>'	end	local function link(title)		return '[[' .. title .. ']]'	end	local function message(text, isgood)		local color = isgood and 'green' or 'darkred'		return '<span style="color:' .. color .. ';">' .. text .. '</span>'	end	local result = collection()	for _, item in ipairs(page_pairs) do		local label		local title1 = item[1]		local title2 = item[2]		if title1 == title2 then			label = message('same title', false)		else			local content1 = get_page_content(title1)			local content2 = get_page_content(title2)			if content1 == content2 then				label = message('same content', true)			else				label = message('different', false) .. ' (' .. diff_link(title1, title2) .. ')'			end		end		result:add('*' .. link(title1) .. ' • ' .. link(title2) .. ' • ' .. label)	end	return result:join() .. '\n'endlocal function sections(text)	return {		first = 1,  -- just after the newline at the end of the last heading		this_section = 1,		next_heading = function(self)			local first = self.first			while first <= #text do				local last, heading				first, last, heading = text:find('==+[\t ]*([^\n]-)[\t ]*==+[\t\r ]*\n', first)				if first then					if first == 1 or text:sub(first - 1, first - 1) == '\n' then						self.this_section = first						self.first = last + 1						return heading					end					first = last + 1				else					break				end			end			self.first = #text + 1			return nil		end,		current_section = function(self)			local first = self.this_section			local last = text:find('\n==[^\n]-==[\t\r ]*\n', first)			if not last then				last = -1			end			return text:sub(first, last)		end,	}endlocal function get_tests(frame, tests)	local args = frame.args	local page_title, section_title = args.page, args.section	local show_all = (args.show == 'all')	if not empty(page_title) then		if not empty(tests) then			error('Invoke must not set "page=' .. page_title .. '" if also setting p.tests.', 0)		end		if page_title:sub(1, 2) == '[[' and page_title:sub(-2) == ']]' then			page_title = strip(page_title:sub(3, -3))		end		tests = get_page_content(page_title)		if not empty(section_title) then			local s = sections(tests)			while true do				local heading = s:next_heading()				if heading then					if heading == section_title then						tests = s:current_section()						break					end				else					error('Section "' .. section_title .. '" not found in page [[' .. page_title .. ']].', 0)				end			end		end	end	if type(tests) ~= 'string' then		if type(tests) == 'table' then			return tests		end		error('No tests were specified; see [[Module:Convert/tester/doc]].', 0)	end	if tests:sub(-1) ~= '\n' then		tests = tests .. '\n'	end	local template_count = 0	local all_tests = collection()	for line in (tests):gmatch('([^\n]-)[\t\r ]*\n') do		local template, expected = line:match('^({{.-}})%s*(.-)%s*$')		if template then			template_count = template_count + 1			all_tests:add({ template, expected })		elseif show_all then			all_tests:add({ text = line })		end	end	if template_count == 0 then		error('No templates found; see [[Module:Convert/tester/doc]].', 0)	end	return all_testsendlocal function main(frame, p, worker)	local args = frame.args	local ok, result = pcall(get_tests, frame, p.tests)	if ok then		ok, result = pcall(worker, frame, result, args.template)		if ok then			return result		end	end	return '<strong class="error">Error</strong>\n\n' .. resultendlocal modules = {	-- For convenience, a key defined here can be used to refer to the	-- corresponding list of modules.	convert = {		'Convert',		'Convert/data',		'Convert/text',		'Convert/extra',	},	cs1 = {		'Citation/CS1',		'Citation/CS1/Configuration',	},	cs1all = {		'Citation/CS1',		'Citation/CS1/Configuration',		'Citation/CS1/Whitelist',	},}local p = {}function p.compare(frame)	local pairs = p.pairs	if not pairs then		local args = frame.args		if not args[2] then			local builtins = modules[args[1] or 'convert']			if builtins then				args = builtins			end		end		pairs = {}		for i, title in ipairs(args) do			if not title:find(':', 1, true) then				title = 'Module:' .. title			end			pairs[i] = { title, title .. '/sandbox' }		end	end	local ok, result = pcall(_compare, frame, pairs)	if ok then		return result	end	return '<strong class="error">Error</strong>\n\n' .. resultendp.check_sandbox = p.comparefunction p.make_tests(frame)	return main(frame, p, _make_tests)endfunction p.run_tests(frame)	return main(frame, p, _run_tests)endreturn p