Module:Sandbox/Vergir: Difference between revisionsGive feedback
Testing reworked HeroData: module body (with help from vergir-bot LLM) |
Testing reworked HeroData: parity harness (with help from vergir-bot LLM) |
||
| Line 447: | Line 447: | ||
return table.concat(output, "\n") | return table.concat(output, "\n") | ||
end | end | ||
-------------------------------------------------------------------------------- | |||
-- TEMPORARY parity harness - NOT part of the live module. | |||
-- {{#invoke:Sandbox/Vergir|test|SUITE}} where SUITE is one of: | |||
-- stats | list | scalars | scalings | has_stat | keys | tags | |||
-------------------------------------------------------------------------------- | |||
local old_module = require('Module:HeroData') | |||
local function repr(v, depth) | |||
depth = depth or 0 | |||
if type(v) ~= 'table' then return tostring(v) end | |||
if depth > 3 then return '<deep>' end | |||
local entries = {} | |||
for k, val in pairs(v) do | |||
table.insert(entries, { k = tostring(k), v = val }) | |||
end | |||
table.sort(entries, function(a, b) return a.k < b.k end) | |||
local parts = {} | |||
for _, e in ipairs(entries) do | |||
table.insert(parts, e.k .. '=' .. repr(e.v, depth + 1)) | |||
end | |||
return '{' .. table.concat(parts, ',') .. '}' | |||
end | |||
-- Calls fn(...) and returns a normalised string, so that a raised error on one | |||
-- side compares equal to the same error on the other (line numbers stripped). | |||
local function attempt(fn, ...) | |||
local ok, result = pcall(fn, ...) | |||
if ok then return repr(result) end | |||
return 'ERR:' .. tostring(result):gsub("^.-:%d+: ", "") | |||
end | |||
local function sorted_hero_keys() | |||
local list = {} | |||
for hero_key in pairs(heroes_data) do table.insert(list, hero_key) end | |||
table.sort(list) | |||
return list | |||
end | |||
-- Every key appearing anywhere in the hero data, so sweeps are exhaustive. | |||
local function all_stat_keys() | |||
local seen = {} | |||
for _, hero in pairs(heroes_data) do | |||
local sources = { | |||
hero, | |||
hero.Weapon or {}, | |||
(hero.Weapon and hero.Weapon.AltFire) or {}, | |||
hero.LevelScaling or {}, | |||
hero.SpiritScaling or {}, | |||
} | |||
for _, source in ipairs(sources) do | |||
for stat in pairs(source) do | |||
if type(stat) == 'string' then seen[stat] = true end | |||
end | |||
end | |||
end | |||
local list = {} | |||
for stat in pairs(seen) do table.insert(list, stat) end | |||
table.sort(list) | |||
return list | |||
end | |||
local MAX_ROWS = 50 | |||
local function report(title, checked, mismatches) | |||
local out = { | |||
"'''" .. title .. "'''", | |||
'', | |||
'Comparisons: ' .. checked, | |||
'Mismatches: ' .. #mismatches, | |||
'', | |||
} | |||
if #mismatches == 0 then | |||
table.insert(out, ':<span style="color:#13f278">No differences.</span>') | |||
return table.concat(out, '\n') | |||
end | |||
table.insert(out, '{| class="wikitable"') | |||
table.insert(out, '! Case !! old !! new') | |||
for i, row in ipairs(mismatches) do | |||
if i > MAX_ROWS then | |||
table.insert(out, '|-\n| colspan="3" | ... and ' .. (#mismatches - MAX_ROWS) .. ' more') | |||
break | |||
end | |||
table.insert(out, '|-\n| <code>' .. row[1] .. '</code> || <code>' .. | |||
mw.text.nowiki(row[2]) .. '</code> || <code>' .. mw.text.nowiki(row[3]) .. '</code>') | |||
end | |||
table.insert(out, '|}') | |||
return table.concat(out, '\n') | |||
end | |||
local suites = {} | |||
-- p.get_stat, via get_hero_var | |||
suites.stats = function() | |||
local checked, mismatches = 0, {} | |||
local stat_keys = all_stat_keys() | |||
for _, hero_key in ipairs(sorted_hero_keys()) do | |||
for _, stat in ipairs(stat_keys) do | |||
local frame = { args = { hero_key, stat } } | |||
local a = attempt(old_module.get_hero_var, frame) | |||
local b = attempt(p.get_hero_var, frame) | |||
checked = checked + 1 | |||
if a ~= b then | |||
table.insert(mismatches, { hero_key .. ' / ' .. stat, a, b }) | |||
end | |||
end | |||
end | |||
return report('get_hero_var (p.get_stat)', checked, mismatches) | |||
end | |||
-- p.get_stat, via get_list_elem | |||
suites.list = function() | |||
local checked, mismatches = 0, {} | |||
local stat_keys = all_stat_keys() | |||
for _, hero_key in ipairs(sorted_hero_keys()) do | |||
for _, stat in ipairs(stat_keys) do | |||
for index = 1, 2 do | |||
local frame = { args = { hero_key, stat, tostring(index) } } | |||
local a = attempt(old_module.get_list_elem, frame) | |||
local b = attempt(p.get_list_elem, frame) | |||
checked = checked + 1 | |||
if a ~= b then | |||
table.insert(mismatches, { hero_key .. ' / ' .. stat .. ' [' .. index .. ']', a, b }) | |||
end | |||
end | |||
end | |||
end | |||
return report('get_list_elem (p.get_stat)', checked, mismatches) | |||
end | |||
-- raw_scaling, via get_hero_scalar (both template and no_template modes) | |||
suites.scalars = function() | |||
local checked, mismatches = 0, {} | |||
local stat_keys = all_stat_keys() | |||
for _, hero_key in ipairs(sorted_hero_keys()) do | |||
for _, stat in ipairs(stat_keys) do | |||
for _, scaling_type in ipairs({ 'Level', 'Spirit' }) do | |||
for _, no_template in ipairs({ 'true', '' }) do | |||
local frame = { args = { hero_key, scaling_type, stat, no_template = no_template } } | |||
local a = attempt(old_module.get_hero_scalar, frame) | |||
local b = attempt(p.get_hero_scalar, frame) | |||
checked = checked + 1 | |||
if a ~= b then | |||
table.insert(mismatches, { | |||
hero_key .. ' / ' .. scaling_type .. ' / ' .. stat .. | |||
(no_template == 'true' and ' (raw)' or ''), a, b }) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
return report('get_hero_scalar (raw_scaling)', checked, mismatches) | |||
end | |||
-- The deprecated shim must stay byte-identical for Module:HeroComparisonTable | |||
suites.scalings = function() | |||
local checked, mismatches = 0, {} | |||
local stat_keys = all_stat_keys() | |||
for _, hero_key in ipairs(sorted_hero_keys()) do | |||
local hero_data = heroes_data[hero_key] | |||
for _, stat in ipairs(stat_keys) do | |||
local a = attempt(old_module.get_hero_scaling_data, hero_data, stat) | |||
local b = attempt(p.get_hero_scaling_data, hero_data, stat) | |||
checked = checked + 1 | |||
if a ~= b then | |||
table.insert(mismatches, { hero_key .. ' / ' .. stat, a, b }) | |||
end | |||
end | |||
end | |||
return report('get_hero_scaling_data (deprecated shim)', checked, mismatches) | |||
end | |||
-- EXPECTED to differ: new version is Weapon/AltFire aware | |||
suites.has_stat = function() | |||
local checked, mismatches = 0, {} | |||
local stat_keys = all_stat_keys() | |||
for _, hero_key in ipairs(sorted_hero_keys()) do | |||
for _, stat in ipairs(stat_keys) do | |||
local frame = { args = { hero_key, stat } } | |||
local a = attempt(old_module.hero_has_stat, frame) | |||
local b = attempt(p.hero_has_stat, frame) | |||
checked = checked + 1 | |||
if a ~= b then | |||
table.insert(mismatches, { hero_key .. ' / ' .. stat, a, b }) | |||
end | |||
end | |||
end | |||
return report('hero_has_stat (EXPECTED to widen)', checked, mismatches) | |||
end | |||
-- EXPECTED to differ: new version also accepts a hero_* key | |||
suites.keys = function() | |||
local checked, mismatches = 0, {} | |||
local inputs = {} | |||
for _, hero_key in ipairs(sorted_hero_keys()) do | |||
table.insert(inputs, hero_key) | |||
local name = heroes_data[hero_key]['Name'] | |||
if name then | |||
table.insert(inputs, name) | |||
table.insert(inputs, mw.ustring.upper(name)) | |||
table.insert(inputs, mw.ustring.lower(name)) | |||
end | |||
end | |||
for _, extra in ipairs({ '', ' ', 'Not A Hero', 'hero_nope' }) do | |||
table.insert(inputs, extra) | |||
end | |||
for _, input in ipairs(inputs) do | |||
local frame = { args = { input } } | |||
local a = attempt(old_module.get_hero_key, frame) | |||
local b = attempt(p.get_hero_key, frame) | |||
checked = checked + 1 | |||
if a ~= b then | |||
table.insert(mismatches, { input, a, b }) | |||
end | |||
end | |||
return report('get_hero_key (EXPECTED to widen)', checked, mismatches) | |||
end | |||
-- get_hero_tag must be unchanged; the has_tags predicate may narrow | |||
suites.tags = function() | |||
local checked, mismatches = 0, {} | |||
local names = {} | |||
for _, hero_key in ipairs(sorted_hero_keys()) do | |||
local name = heroes_data[hero_key]['Name'] | |||
if name then table.insert(names, name) end | |||
end | |||
for _, extra in ipairs({ 'Not A Hero', '' }) do table.insert(names, extra) end | |||
for _, name in ipairs(names) do | |||
for _, index in ipairs({ '0', '1', '2', '3', '4', 'x' }) do | |||
local frame = { args = { name, index } } | |||
local a = attempt(old_module.get_hero_tag, frame) | |||
local b = attempt(p.get_hero_tag, frame) | |||
checked = checked + 1 | |||
if a ~= b then | |||
table.insert(mismatches, { name .. ' [' .. index .. ']', a, b }) | |||
end | |||
end | |||
-- Predicate: old Infobox_hero.hero_has_tags vs new p.has_tags | |||
local old_tag = old_module.get_hero_tag({ args = { name, '1' } }) | |||
local old_pred = tostring(string.find(old_tag, 'Error:') == nil) | |||
local new_pred = tostring(p.has_tags({ args = { name } }) == 'true') | |||
checked = checked + 1 | |||
if old_pred ~= new_pred then | |||
table.insert(mismatches, { 'PREDICATE ' .. name, old_pred, new_pred }) | |||
end | |||
end | |||
return report('get_hero_tag + has_tags predicate', checked, mismatches) | |||
end | |||
function p.test(frame) | |||
local suite = frame.args[1] | |||
if suite == nil or suites[suite] == nil then | |||
local names = {} | |||
for name in pairs(suites) do table.insert(names, name) end | |||
table.sort(names) | |||
return 'Pick a suite: ' .. table.concat(names, ', ') | |||
end | |||
return suites[suite]() | |||
end | |||
return p | |||