<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.cern.ch/index.php?action=history&amp;feed=atom&amp;title=Module%3AConvert%2Ftester</id>
	<title>Module:Convert/tester - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.cern.ch/index.php?action=history&amp;feed=atom&amp;title=Module%3AConvert%2Ftester"/>
	<link rel="alternate" type="text/html" href="https://wiki.cern.ch/index.php?title=Module:Convert/tester&amp;action=history"/>
	<updated>2026-04-05T18:09:26Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.44.0</generator>
	<entry>
		<id>https://wiki.cern.ch/index.php?title=Module:Convert/tester&amp;diff=4901&amp;oldid=prev</id>
		<title>Bbergia: 1 revision imported</title>
		<link rel="alternate" type="text/html" href="https://wiki.cern.ch/index.php?title=Module:Convert/tester&amp;diff=4901&amp;oldid=prev"/>
		<updated>2025-12-01T10:29:34Z</updated>

		<summary type="html">&lt;p&gt;1 revision imported&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 10:29, 1 December 2025&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-notice&quot; lang=&quot;en&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(No difference)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary>
		<author><name>Bbergia</name></author>
	</entry>
	<entry>
		<id>https://wiki.cern.ch/index.php?title=Module:Convert/tester&amp;diff=4900&amp;oldid=prev</id>
		<title>wikipedia&gt;Johnuniq: fix typo in regex reported by DePiep</title>
		<link rel="alternate" type="text/html" href="https://wiki.cern.ch/index.php?title=Module:Convert/tester&amp;diff=4900&amp;oldid=prev"/>
		<updated>2023-04-30T23:44:41Z</updated>

		<summary type="html">&lt;p&gt;fix typo in regex reported by DePiep&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- Test the output from a template by comparing it with fixed text.&lt;br /&gt;
-- The expected text must be in a single line, but can include&lt;br /&gt;
-- &amp;quot;\n&amp;quot; (two characters) to indicate that a newline is expected.&lt;br /&gt;
-- Tests are run (or created) by setting p.tests (string or table), or&lt;br /&gt;
-- by setting page=PAGE_TITLE (and optionally section=SECTION_TITLE),&lt;br /&gt;
-- then executing run_tests (or make_tests).&lt;br /&gt;
&lt;br /&gt;
local Collection = {}&lt;br /&gt;
Collection.__index = Collection&lt;br /&gt;
do&lt;br /&gt;
	function Collection:add(item)&lt;br /&gt;
		if item ~= nil then&lt;br /&gt;
			self.n = self.n + 1&lt;br /&gt;
			self[self.n] = item&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	function Collection:join(sep)&lt;br /&gt;
		return table.concat(self, sep)&lt;br /&gt;
	end&lt;br /&gt;
	function Collection.new()&lt;br /&gt;
		return setmetatable({n = 0}, Collection)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function empty(text)&lt;br /&gt;
	-- Return true if text is nil or empty (assuming a string).&lt;br /&gt;
	return text == nil or text == &amp;#039;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function strip(text)&lt;br /&gt;
	-- Return text with no leading/trailing whitespace.&lt;br /&gt;
	return text:match(&amp;quot;^%s*(.-)%s*$&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function normalize(text)&lt;br /&gt;
	-- Return text with any strip markers normalized by replacing the&lt;br /&gt;
	-- unique number with a fixed value so comparisons work.&lt;br /&gt;
	return text:gsub(&amp;#039;(\127[^\127]*UNIQ[^\127]*%-)(%x+)(-QINU[^\127]*\127)&amp;#039;, &amp;#039;%100000000%3&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function status_box(stats, expected, actual, iscomment)&lt;br /&gt;
	local label, bgcolor, align, isfail&lt;br /&gt;
	if iscomment then&lt;br /&gt;
		actual = &amp;#039;&amp;#039;&lt;br /&gt;
		align = &amp;#039;center&amp;#039;&lt;br /&gt;
		bgcolor = &amp;#039;silver&amp;#039;&lt;br /&gt;
		label = &amp;#039;Cmnt&amp;#039;&lt;br /&gt;
	elseif expected == &amp;#039;&amp;#039; then&lt;br /&gt;
		stats.ignored = stats.ignored + 1&lt;br /&gt;
		return &amp;#039;&amp;#039;, actual&lt;br /&gt;
	elseif normalize(expected) == normalize(actual) then&lt;br /&gt;
		stats.pass = stats.pass + 1&lt;br /&gt;
		actual = &amp;#039;&amp;#039;&lt;br /&gt;
		align = &amp;#039;center&amp;#039;&lt;br /&gt;
		bgcolor = &amp;#039;green&amp;#039;&lt;br /&gt;
		label = &amp;#039;Pass&amp;#039;&lt;br /&gt;
	else&lt;br /&gt;
		stats.fail = stats.fail + 1&lt;br /&gt;
		align = &amp;#039;center&amp;#039;&lt;br /&gt;
		bgcolor = &amp;#039;red&amp;#039;&lt;br /&gt;
		label = &amp;#039;Fail&amp;#039;&lt;br /&gt;
		isfail = true&lt;br /&gt;
	end&lt;br /&gt;
	local sbox = &amp;#039;style=&amp;quot;text-align:&amp;#039; .. align .. &amp;#039;;color:white;background:&amp;#039; .. bgcolor .. &amp;#039;;&amp;quot; | &amp;#039; .. label&lt;br /&gt;
	return sbox, actual, isfail&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function status_text(stats)&lt;br /&gt;
	local bgcolor, ignored_text, msg, ttext&lt;br /&gt;
	if stats.template then&lt;br /&gt;
		ttext = &amp;quot;&amp;#039;&amp;#039;&amp;#039;Using [[Template:&amp;quot; .. stats.template .. &amp;quot;]]:&amp;#039;&amp;#039;&amp;#039; &amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		ttext = &amp;#039;&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	if stats.fail == 0 then&lt;br /&gt;
		if stats.pass == 0 then&lt;br /&gt;
			bgcolor = &amp;#039;salmon&amp;#039;&lt;br /&gt;
			msg = &amp;#039;No tests performed&amp;#039;&lt;br /&gt;
		else&lt;br /&gt;
			bgcolor = &amp;#039;green&amp;#039;&lt;br /&gt;
			msg = string.format(&amp;#039;All %d tests passed&amp;#039;, stats.pass)&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		bgcolor = &amp;#039;darkred&amp;#039;&lt;br /&gt;
		msg = string.format(&amp;#039;%d test%s failed&amp;#039;, stats.fail, stats.fail == 1 and &amp;#039;&amp;#039; or &amp;#039;s&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	if stats.ignored == 0 then&lt;br /&gt;
		ignored_text = &amp;#039;&amp;#039;&lt;br /&gt;
	else&lt;br /&gt;
		bgcolor = &amp;#039;salmon&amp;#039;&lt;br /&gt;
		ignored_text = string.format(&amp;#039;, %d test%s ignored because expected text is blank&amp;#039;, stats.ignored, stats.ignored == 1 and &amp;#039;&amp;#039; or &amp;#039;s&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	return ttext .. &amp;#039;&amp;lt;span style=&amp;quot;font-size:120%;color:white;background-color:&amp;#039; .. bgcolor .. &amp;#039;;&amp;quot;&amp;gt;&amp;#039; ..&lt;br /&gt;
		msg .. ignored_text .. &amp;#039;.&amp;lt;/span&amp;gt;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function run_template(frame, template, args, collapse_multiline)&lt;br /&gt;
	-- Template &amp;quot;{{ example |  2  =  def  |  abc  |  name  =  ghi jkl  }}&amp;quot;&lt;br /&gt;
	-- gives xargs { &amp;quot;  abc  &amp;quot;, &amp;quot;def&amp;quot;, name = &amp;quot;ghi jkl&amp;quot; }.&lt;br /&gt;
	if template:sub(1, 2) == &amp;#039;{{&amp;#039; and template:sub(-2, -1) == &amp;#039;}}&amp;#039; then&lt;br /&gt;
		template = template:sub(3, -3) .. &amp;#039;|&amp;#039;  -- append sentinel to get last field&lt;br /&gt;
	else&lt;br /&gt;
		return &amp;#039;(invalid template)&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	local xargs = {}&lt;br /&gt;
	local index = 1&lt;br /&gt;
	local templatename&lt;br /&gt;
	local function put_arg(k, v)&lt;br /&gt;
		-- Kludge: Module:Val uses Module:Arguments which trims arguments and&lt;br /&gt;
		-- omits blank arguments. Simulate that here.&lt;br /&gt;
		-- LATER Need a parameter to control this.&lt;br /&gt;
		if templatename:sub(1, 3) == &amp;#039;val&amp;#039; then&lt;br /&gt;
			v = strip(v)&lt;br /&gt;
			if v == &amp;#039;&amp;#039; then&lt;br /&gt;
				return&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		xargs[k] = v&lt;br /&gt;
	end&lt;br /&gt;
	template = template:gsub(&amp;#039;(%[%[[^%[%]]-)|(.-%]%])&amp;#039;, &amp;#039;%1\0%2&amp;#039;)  -- replace pipe in piped link with a zero byte&lt;br /&gt;
	for field in template:gmatch(&amp;#039;(.-)|&amp;#039;) do&lt;br /&gt;
		field = field:gsub(&amp;#039;%z&amp;#039;, &amp;#039;|&amp;#039;)  -- restore pipe in piped link&lt;br /&gt;
		if templatename == nil then&lt;br /&gt;
			templatename = args.template or strip(field)&lt;br /&gt;
			if templatename == &amp;#039;&amp;#039; then&lt;br /&gt;
				return &amp;#039;(invalid template)&amp;#039;&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			local k, eq, v = field:match(&amp;quot;^(.-)(=)(.*)$&amp;quot;)&lt;br /&gt;
			if eq then&lt;br /&gt;
				k, v = strip(k), strip(v)  -- k and/or v can be empty&lt;br /&gt;
				local i = tonumber(k)&lt;br /&gt;
				if i and i &amp;gt; 0 and string.match(k, &amp;#039;^%d+$&amp;#039;) then&lt;br /&gt;
					put_arg(i, v)&lt;br /&gt;
				else&lt;br /&gt;
					put_arg(k, v)&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				while xargs[index] ~= nil do&lt;br /&gt;
					-- Skip any explicit numbered parameters like &amp;quot;|5=five&amp;quot;.&lt;br /&gt;
					index = index + 1&lt;br /&gt;
				end&lt;br /&gt;
				put_arg(index, field)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if args.test and not xargs.test then&lt;br /&gt;
		-- For convert, allow test=preview or test=nopreview to be injected into&lt;br /&gt;
		-- the convert under test, if it does not already use that parameter.&lt;br /&gt;
		-- That allows, for example, a preview of make_tests to show nopreview results.&lt;br /&gt;
		xargs.test = args.test&lt;br /&gt;
	end&lt;br /&gt;
	local function expand(t)&lt;br /&gt;
		return frame:expandTemplate(t)&lt;br /&gt;
	end&lt;br /&gt;
	local ok, result = pcall(expand, { title = templatename, args = xargs })&lt;br /&gt;
	if not ok then&lt;br /&gt;
		result = &amp;#039;Error: &amp;#039; .. result&lt;br /&gt;
	end&lt;br /&gt;
	if collapse_multiline then&lt;br /&gt;
		result = result:gsub(&amp;#039;\n&amp;#039;, &amp;#039;\\n&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	return result&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function _make_tests(frame, all_tests, args)&lt;br /&gt;
	local maxlen = 38&lt;br /&gt;
	for _, item in ipairs(all_tests) do&lt;br /&gt;
		local template = item[1]&lt;br /&gt;
		if template then&lt;br /&gt;
			local templen = mw.ustring.len(template)&lt;br /&gt;
			item.templen = templen&lt;br /&gt;
			if maxlen &amp;lt; templen and templen &amp;lt;= 70 then&lt;br /&gt;
				maxlen = templen&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local result = Collection.new()&lt;br /&gt;
	for _, item in ipairs(all_tests) do&lt;br /&gt;
		local template = item[1]&lt;br /&gt;
		if template then&lt;br /&gt;
			local actual = run_template(frame, template, args, true)&lt;br /&gt;
			local pad = string.rep(&amp;#039; &amp;#039;, maxlen - item.templen) .. &amp;#039;  &amp;#039;&lt;br /&gt;
			result:add(template .. pad .. actual)&lt;br /&gt;
		else&lt;br /&gt;
			local text = item.text&lt;br /&gt;
			if text then&lt;br /&gt;
				result:add(text)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- Pre tags returned by a module are html tags, not like wikitext &amp;lt;pre&amp;gt;...&amp;lt;/pre&amp;gt;.&lt;br /&gt;
	return &amp;#039;&amp;lt;pre&amp;gt;\n&amp;#039; .. mw.text.nowiki(result:join(&amp;#039;\n&amp;#039;)) .. &amp;#039;\n&amp;lt;/pre&amp;gt;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function _run_tests(frame, all_tests, args)&lt;br /&gt;
	local function safe_cell(text, multiline)&lt;br /&gt;
		-- For testing {{convert}}, want wikitext like &amp;#039;[[kilogram|kg]]&amp;#039; to be unchanged&lt;br /&gt;
		-- so the link works and so the displayed text is short (just &amp;quot;kg&amp;quot; in example).&lt;br /&gt;
		text = text:gsub(&amp;#039;(%[%[[^%[%]]-)|(.-%]%])&amp;#039;, &amp;#039;%1\0%2&amp;#039;)  -- replace pipe in piped link with a zero byte&lt;br /&gt;
		text = text:gsub(&amp;#039;{&amp;#039;, &amp;#039;&amp;amp;#123;&amp;#039;):gsub(&amp;#039;|&amp;#039;, &amp;#039;&amp;amp;#124;&amp;#039;)    -- escape &amp;#039;{&amp;#039; and &amp;#039;|&amp;#039;&lt;br /&gt;
		text = text:gsub(&amp;#039;%z&amp;#039;, &amp;#039;|&amp;#039;)                            -- restore pipe in piped link&lt;br /&gt;
		if multiline then&lt;br /&gt;
			text = text:gsub(&amp;#039;\\n&amp;#039;, &amp;#039;&amp;lt;br /&amp;gt;&amp;#039;)&lt;br /&gt;
		end&lt;br /&gt;
		return text&lt;br /&gt;
	end&lt;br /&gt;
	local function nowiki_cell(text, multiline)&lt;br /&gt;
		text = mw.text.nowiki(text)&lt;br /&gt;
		if multiline then&lt;br /&gt;
			text = text:gsub(&amp;#039;\\n&amp;#039;, &amp;#039;&amp;lt;br /&amp;gt;&amp;#039;)&lt;br /&gt;
		end&lt;br /&gt;
		return text&lt;br /&gt;
	end&lt;br /&gt;
	local stats = { pass = 0, fail = 0, ignored = 0, template = args.template }&lt;br /&gt;
	local result = Collection.new()&lt;br /&gt;
	result:add(&amp;#039;{| class=&amp;quot;wikitable sortable&amp;quot;&amp;#039;)&lt;br /&gt;
	result:add(&amp;#039;! Template !! Expected !! Actual, if different !! Status&amp;#039;)&lt;br /&gt;
	for _, item in ipairs(all_tests) do&lt;br /&gt;
		local template, expected = item[1], item[2] or &amp;#039;&amp;#039;&lt;br /&gt;
		if template then&lt;br /&gt;
			local actual = run_template(frame, template, args, true)&lt;br /&gt;
			local sbox, actual, isfail = status_box(stats, expected, actual)&lt;br /&gt;
			result:add(&amp;#039;|-&amp;#039;)&lt;br /&gt;
			result:add(&amp;#039;| &amp;#039; .. safe_cell(template))&lt;br /&gt;
			result:add(&amp;#039;| &amp;#039; .. safe_cell(expected, true))&lt;br /&gt;
			result:add(&amp;#039;| &amp;#039; .. safe_cell(actual, true))&lt;br /&gt;
			result:add(&amp;#039;| &amp;#039; .. sbox)&lt;br /&gt;
			if isfail then&lt;br /&gt;
				result:add(&amp;#039;|-&amp;#039;)&lt;br /&gt;
				result:add(&amp;#039;| align=&amp;quot;center&amp;quot;| (above, nowiki)&amp;#039;)&lt;br /&gt;
				result:add(&amp;#039;| &amp;#039; .. nowiki_cell(normalize(expected), true))&lt;br /&gt;
				result:add(&amp;#039;| &amp;#039; .. nowiki_cell(normalize(actual), true))&lt;br /&gt;
				result:add(&amp;#039;|&amp;#039;)&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			local text = item.text&lt;br /&gt;
			if text and text:sub(1, 3) == &amp;#039;---&amp;#039; then&lt;br /&gt;
				result:add(&amp;#039;|-&amp;#039;)&lt;br /&gt;
				result:add(&amp;#039;| colspan=&amp;quot;3&amp;quot; style=&amp;quot;color:white;background:silver;&amp;quot; | &amp;#039; .. safe_cell(strip(text:sub(4)), true))&lt;br /&gt;
				result:add(&amp;#039;| &amp;#039; .. status_box(stats, &amp;#039;&amp;#039;, &amp;#039;&amp;#039;, true))&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	result:add(&amp;#039;|}&amp;#039;)&lt;br /&gt;
	return status_text(stats) .. &amp;#039;\n\n&amp;#039; .. result:join(&amp;#039;\n&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_page_content(page_title, ignore_error)&lt;br /&gt;
	local t = mw.title.new(page_title)&lt;br /&gt;
	if t then&lt;br /&gt;
		local content = t:getContent()&lt;br /&gt;
		if content then&lt;br /&gt;
			if content:sub(-1) ~= &amp;#039;\n&amp;#039; then&lt;br /&gt;
				content = content .. &amp;#039;\n&amp;#039;&lt;br /&gt;
			end&lt;br /&gt;
			return content&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if not ignore_error then&lt;br /&gt;
		error(&amp;#039;Could not read wikitext from &amp;quot;[[&amp;#039; .. page_title .. &amp;#039;]]&amp;quot;.&amp;#039;, 0)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function _compare(frame, page_pairs)&lt;br /&gt;
	local prefix = frame.args.prefix or &amp;#039;*&amp;#039;&lt;br /&gt;
	local function diff_link(title1, title2)&lt;br /&gt;
		return &amp;#039;&amp;lt;span class=&amp;quot;plainlinks&amp;quot;&amp;gt;[&amp;#039; ..&lt;br /&gt;
			tostring(mw.uri.fullUrl(&amp;#039;Special:ComparePages&amp;#039;,&lt;br /&gt;
				{ page1 = title1, page2 = title2 })) ..&lt;br /&gt;
			&amp;#039; diff]&amp;lt;/span&amp;gt;&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	local function link(title)&lt;br /&gt;
		return &amp;#039;[[&amp;#039; .. title .. &amp;#039;]]&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	local function message(text, isgood)&lt;br /&gt;
		local color = isgood and &amp;#039;green&amp;#039; or &amp;#039;darkred&amp;#039;&lt;br /&gt;
		return &amp;#039;&amp;lt;span style=&amp;quot;color:&amp;#039; .. color .. &amp;#039;;&amp;quot;&amp;gt;&amp;#039; .. text .. &amp;#039;&amp;lt;/span&amp;gt;&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	local result = Collection.new()&lt;br /&gt;
	for _, item in ipairs(page_pairs) do&lt;br /&gt;
		local label&lt;br /&gt;
		local title1 = item[1]&lt;br /&gt;
		local title2 = item[2]&lt;br /&gt;
		if title1 == title2 then&lt;br /&gt;
			label = message(&amp;#039;same title&amp;#039;, false)&lt;br /&gt;
		else&lt;br /&gt;
			local content1 = get_page_content(title1, true)&lt;br /&gt;
			local content2 = get_page_content(title2, true)&lt;br /&gt;
			if not content1 or not content2 then&lt;br /&gt;
				label = message(&amp;#039;does not exist&amp;#039;, false)&lt;br /&gt;
			elseif content1 == content2 then&lt;br /&gt;
				label = message(&amp;#039;same content&amp;#039;, true)&lt;br /&gt;
			else&lt;br /&gt;
				label = message(&amp;#039;different&amp;#039;, false) .. &amp;#039; (&amp;#039; .. diff_link(title1, title2) .. &amp;#039;)&amp;#039;&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		result:add(prefix .. link(title1) .. &amp;#039; • &amp;#039; .. link(title2) .. &amp;#039; • &amp;#039; .. label)&lt;br /&gt;
	end&lt;br /&gt;
	return result:join(&amp;#039;\n&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function sections(text)&lt;br /&gt;
	return {&lt;br /&gt;
		first = 1,  -- just after the newline at the end of the last heading&lt;br /&gt;
		this_section = 1,&lt;br /&gt;
		next_heading = function(self)&lt;br /&gt;
			local first = self.first&lt;br /&gt;
			while first &amp;lt;= #text do&lt;br /&gt;
				local last, heading&lt;br /&gt;
				first, last, heading = text:find(&amp;#039;==+[\t ]*([^\n]-)[\t ]*==+[\t\r ]*\n&amp;#039;, first)&lt;br /&gt;
				if first then&lt;br /&gt;
					if first == 1 or text:sub(first - 1, first - 1) == &amp;#039;\n&amp;#039; then&lt;br /&gt;
						self.this_section = first&lt;br /&gt;
						self.first = last + 1&lt;br /&gt;
						return heading&lt;br /&gt;
					end&lt;br /&gt;
					first = last + 1&lt;br /&gt;
				else&lt;br /&gt;
					break&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			self.first = #text + 1&lt;br /&gt;
			return nil&lt;br /&gt;
		end,&lt;br /&gt;
		current_section = function(self)&lt;br /&gt;
			local first = self.this_section&lt;br /&gt;
			local last = text:find(&amp;#039;\n==[^\n]-==[\t\r ]*\n&amp;#039;, first)&lt;br /&gt;
			if not last then&lt;br /&gt;
				last = -1&lt;br /&gt;
			end&lt;br /&gt;
			return text:sub(first, last)&lt;br /&gt;
		end,&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_tests(frame, tests)&lt;br /&gt;
	local args = frame.args&lt;br /&gt;
	local page_title, section_title = args.page, args.section&lt;br /&gt;
	local show_all = (args.show == &amp;#039;all&amp;#039;)&lt;br /&gt;
	if not empty(page_title) then&lt;br /&gt;
		if not empty(tests) then&lt;br /&gt;
			error(&amp;#039;Invoke must not set &amp;quot;page=&amp;#039; .. page_title .. &amp;#039;&amp;quot; if also setting p.tests.&amp;#039;, 0)&lt;br /&gt;
		end&lt;br /&gt;
		if page_title:sub(1, 2) == &amp;#039;[[&amp;#039; and page_title:sub(-2) == &amp;#039;]]&amp;#039; then&lt;br /&gt;
			page_title = strip(page_title:sub(3, -3))&lt;br /&gt;
		end&lt;br /&gt;
		tests = get_page_content(page_title)&lt;br /&gt;
		if not empty(section_title) then&lt;br /&gt;
			local s = sections(tests)&lt;br /&gt;
			while true do&lt;br /&gt;
				local heading = s:next_heading()&lt;br /&gt;
				if heading then&lt;br /&gt;
					if heading == section_title then&lt;br /&gt;
						tests = s:current_section()&lt;br /&gt;
						break&lt;br /&gt;
					end&lt;br /&gt;
				else&lt;br /&gt;
					error(&amp;#039;Section &amp;quot;&amp;#039; .. section_title .. &amp;#039;&amp;quot; not found in page [[&amp;#039; .. page_title .. &amp;#039;]].&amp;#039;, 0)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if type(tests) ~= &amp;#039;string&amp;#039; then&lt;br /&gt;
		if type(tests) == &amp;#039;table&amp;#039; then&lt;br /&gt;
			return tests&lt;br /&gt;
		end&lt;br /&gt;
		error(&amp;#039;No tests were specified; see [[Module:Convert/tester/doc]].&amp;#039;, 0)&lt;br /&gt;
	end&lt;br /&gt;
	if tests:sub(-1) ~= &amp;#039;\n&amp;#039; then&lt;br /&gt;
		tests = tests .. &amp;#039;\n&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	local template_count = 0&lt;br /&gt;
	local all_tests = Collection.new()&lt;br /&gt;
	for line in (tests):gmatch(&amp;#039;([^\n]-)[\t\r ]*\n&amp;#039;) do&lt;br /&gt;
		local template, expected = line:match(&amp;#039;^({{.-}})%s*(.-)%s*$&amp;#039;)&lt;br /&gt;
		if template then&lt;br /&gt;
			template_count = template_count + 1&lt;br /&gt;
			all_tests:add({ template, expected })&lt;br /&gt;
		elseif show_all then&lt;br /&gt;
			all_tests:add({ text = line })&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if template_count == 0 then&lt;br /&gt;
		error(&amp;#039;No templates found; see [[Module:Convert/tester/doc]].&amp;#039;, 0)&lt;br /&gt;
	end&lt;br /&gt;
	return all_tests&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function main(frame, p, worker)&lt;br /&gt;
	local ok, result = pcall(get_tests, frame, p.tests)&lt;br /&gt;
	if ok then&lt;br /&gt;
		ok, result = pcall(worker, frame, result, frame.args)&lt;br /&gt;
		if ok then&lt;br /&gt;
			return result&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;#039;&amp;lt;strong class=&amp;quot;error&amp;quot;&amp;gt;Error&amp;lt;/strong&amp;gt;\n\n&amp;#039; .. result&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local modules = {&lt;br /&gt;
	-- For convenience, a key defined here can be used to refer to the&lt;br /&gt;
	-- corresponding list of modules.&lt;br /&gt;
	countries = {  -- Commons&lt;br /&gt;
		&amp;#039;Countries&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/Africa&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/Americas&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/Arab world&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/Asia&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/Caribbean&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/Central America&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/Europe&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/North America&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/North America (subcontinent)&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/Oceania&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/South America&amp;#039;,&lt;br /&gt;
		&amp;#039;Countries/United Kingdom&amp;#039;,&lt;br /&gt;
	},&lt;br /&gt;
	convert = {&lt;br /&gt;
		&amp;#039;Convert&amp;#039;,&lt;br /&gt;
		&amp;#039;Convert/data&amp;#039;,&lt;br /&gt;
		&amp;#039;Convert/text&amp;#039;,&lt;br /&gt;
		&amp;#039;Convert/extra&amp;#039;,&lt;br /&gt;
		&amp;#039;Convert/wikidata&amp;#039;,&lt;br /&gt;
		&amp;#039;Convert/wikidata/data&amp;#039;,&lt;br /&gt;
	},&lt;br /&gt;
	cs1 = {&lt;br /&gt;
		&amp;#039;Citation/CS1&amp;#039;,&lt;br /&gt;
		&amp;#039;Citation/CS1/Configuration&amp;#039;,&lt;br /&gt;
	},&lt;br /&gt;
	cs1all = {&lt;br /&gt;
		&amp;#039;Citation/CS1&amp;#039;,&lt;br /&gt;
		&amp;#039;Citation/CS1/Configuration&amp;#039;,&lt;br /&gt;
		&amp;#039;Citation/CS1/Whitelist&amp;#039;,&lt;br /&gt;
		&amp;#039;Citation/CS1/Date validation&amp;#039;,&lt;br /&gt;
	},&lt;br /&gt;
	team = {&lt;br /&gt;
		&amp;#039;Team appearances list&amp;#039;,&lt;br /&gt;
		&amp;#039;Team appearances list/data&amp;#039;,&lt;br /&gt;
		&amp;#039;Team appearances list/show&amp;#039;,&lt;br /&gt;
	},&lt;br /&gt;
	val = {&lt;br /&gt;
		&amp;#039;Val&amp;#039;,&lt;br /&gt;
		&amp;#039;Val/units&amp;#039;,&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
function p.compare(frame)&lt;br /&gt;
	local page_pairs = p.pairs&lt;br /&gt;
	if not page_pairs then&lt;br /&gt;
		local args = frame.args&lt;br /&gt;
		if not args[2] then&lt;br /&gt;
			local builtins = modules[args[1] or &amp;#039;convert&amp;#039;]&lt;br /&gt;
			if builtins then&lt;br /&gt;
				args = builtins&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		page_pairs = {}&lt;br /&gt;
		for i, title in ipairs(args) do&lt;br /&gt;
			if not title:find(&amp;#039;:&amp;#039;, 1, true) then&lt;br /&gt;
				title = &amp;#039;Module:&amp;#039; .. title&lt;br /&gt;
			end&lt;br /&gt;
			page_pairs[i] = { title, title .. &amp;#039;/sandbox&amp;#039; }&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local ok, result = pcall(_compare, frame, page_pairs)&lt;br /&gt;
	if ok then&lt;br /&gt;
		return result&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;#039;&amp;lt;strong class=&amp;quot;error&amp;quot;&amp;gt;Error&amp;lt;/strong&amp;gt;\n\n&amp;#039; .. result&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
p.check_sandbox = p.compare&lt;br /&gt;
&lt;br /&gt;
function p.make_tests(frame)&lt;br /&gt;
	return main(frame, p, _make_tests)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.run_tests(frame)&lt;br /&gt;
	return main(frame, p, _run_tests)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>wikipedia&gt;Johnuniq</name></author>
	</entry>
</feed>