Jump to content

Module:UnitTests/sandbox

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Darklama (talk | contribs) at 18:04, 29 May 2013 (partial undo). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
-- UnitTester provides unit testing for other Lua scripts. For details see [[Wikipedia:Lua#Unit_testing]].
-- For user documentation see talk page.
local UnitTester = {}
local failures, results, tick, cross = 0;
local table = table;

function first_difference(s1, s2)
    local max = math.min(#s1, #s2)
    for i = 1, max do
        if s1:sub(i,i) ~= s2:sub(i,i) then return i end
    end
    return max+1;
end

function nowiki(text)
        return string.gsub(mw.text.nowiki(text), '[\n ]', {['\n']='<br/>',[' ']='&#32;'});
end

function UnitTester:equals(name, actual, expected, options)
    local name, differs = nowiki(name), nil;
    local nowiki = options and options.nowiki and nowiki or function(text) return text; end
    local expected, actual = nowiki(expected), nowiki(actual);
    if expected == actual then
        table.insert(results, table.concat{ '\n|- class="test-pass" \n| ', tick });
    else
        table.insert(results, table.concat{ '\n|- class="test-fail" \n| ', cross });
        differs = self.differs_at and first_difference(expected, actual);
        failures = failures + 1;
    end
    table.insert(results, table.concat({ '', name, expected, actual, differs }, '\n|\n') );
end

function UnitTester:preprocess_equals(text, expected, options)
    return self:equals(text, self.frame:preprocess(text), expected, options );
end

function UnitTester:preprocess_equals_many(prefix, suffix, cases, options)
    for _, case in ipairs(cases) do
        self:preprocess_equals(table.concat{prefix, case[1], suffix}, case[2], options);
    end
end

function UnitTester:preprocess_equals_preprocess(text1, text2, options)
    self:equals(text1, self.frame:preprocess(text1), self.frame:preprocess(text2), options);
end

function UnitTester:preprocess_equals_preprocess_many(prefix1, suffix1, prefix2, suffix2, cases, options)
    for _, case in ipairs(cases) do
        self:preprocess_equals_preprocess(
            table.concat{prefix1, case[1], suffix1},
            table.concat{prefix2, case[2] or case[1], suffix2},
            options
        );
    end
end

local function deep_compare(t1, t2, ignore_mt)
    local ty1, ty2 = type(t1), type(t2)
    if ty1 ~= ty2 then return false end
    if ty1 ~= 'table' then return t1 == t2 end
    local mt = getmetatable(t1)
    if not ignore_mt and mt and mt.__eq then return t1 == t2 end
    for k, v1 in pairs(t1) do
        local v2 = t2[k]
        if v2 == nil or not deep_compare(v1, v2, ignore_mt) then return false end
    end
    for k, v2 in pairs(t2) do
        local v1 = t1[k]
        if v1 == nil or not deep_compare(v1, v2, ignore_mt) then return false end
    end
    return true
end
 
function serialize(v)
    if type(v) == 'string' then
        return string.format("%q", string.gsub(v, '\n', '\\n'));
    elseif type(v) ~= 'table' then
        return tostring(v);
    else
        local result = {};
        for key, val in pairs(v) do
            if type(key) == 'number' then
                table.insert(result, serialize(val));
            else
                table.insert(result, table.concat{'[', serialize(k), '] = ', serialize(val)})
            end
        end
        return table.concat{'{', table.concat(result, ','), '}'};
    end
end

function UnitTester:equals_deep(name, actual, expected, options)
    local name, differs = nowiki(name), nil;
    local nowiki = options and options.nowiki and nowiki or function(text) return text; end
    if deep_compare(actual, expected) then
        table.insert(results, table.concat{ '\n|- class="test-pass" \n| ', tick });
    else
        table.insert(results, table.concat{ '\n|- class="test-fail" \n| ', cross });
        differs = self.differs_at and first_difference(expected, actual);
        failures = failures + 1;
    end
    local expected, actual = nowiki(serialize(expected)), nowiki(serialize(actual));
    table.insert(results, table.concat({ '', name, expected, actual, differs }, '\n|\n') );
end

function UnitTester:run(frame)
    local table_header = '{| class="wikitable unittests"\n! !! Text !! Expected !! Actual';
    tick, cross = frame:preprocess('{{tick}}'), frame:preprocess('{{cross}}');

    results = {};
    self.frame = frame;
    self.differs_at = frame.args.differs_at;
    if self.differs_at then
        table_header = table_header .. ' !! Differs at';
    end
    for i,test in ipairs(self.tests) do
        table.insert(results, table.concat{ "'''", test, "''':\n", table_header });
        self[test](self, frame);
        table.insert(results, "\n|}\n\n");
    end
    table.insert(results, 1, string.format(
        '<span style="color:%s; font-weight:bold;">%s</span>\n\n',
        failures == 0 and "#008000" or "#800000",
        failures == 0 and 'All tests passed.' or string.format('%d tests failed.', failures)
    ));
    return table.concat(results);
end

function UnitTester:new()
    local o = setmetatable({ tests = {} }, self)
    function o.run_tests(frame) return o:run(frame) end;
    self.__index = self;
    self.__newindex = function(t, k, v)
        if type(k) == 'string' and type(v) == 'function' then
            table.insert(t.tests, k);
        end
        rawset(t, k, v);
    end
    return o
end

local p = UnitTester:new();
return p