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 19:05, 16 May 2014 (allow chaining and maybe improve speed). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
-- UnitTests provides unit testing for other Lua scripts. For details see [[Wikipedia:Lua#Unit_testing]].
-- For user documentation see talk page.
local UnitTests = {}
local tick, cross;
local table, insert, concat = table, table.insert, table.concat;

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 UnitTests:equals(name, got, want, options)
    local name, differs = nowiki(name), nil;
    local nowiki = options and options.nowiki and nowiki or function(text) return text; end
    local want, got = nowiki(want), nowiki(got);
    if want == got then
        insert(self.results, concat{ '|- class="test-pass"\n| ', tick });
    else
        insert(self.results, concat{ '|- class="test-fail"\n| ', cross });
        differs = self.differs_at and first_difference(want, got);
        self.failures = self.failures + 1;
    end
    insert(self.results, concat({ '', name, want, got, differs }, '\n|') );
    return self;
end

function UnitTests:preprocess_equals(got, want, options)
    return self:equals(got, self.frame:preprocess(got), want, options );
end

function UnitTests:preprocess_equals_many(prefix, suffix, cases, options)
	local frame, got_case = self.frame;
    for _, case in ipairs(cases) do
    	got_case = concat{prefix, case[1], suffix};
        self:equals(got_case, frame:preprocess(got_case), case[2], options);
    end
    return self;
end

function UnitTests:preprocess_equals_preprocess(got, want, options)
	local frame = self.frame;
    return self:equals(got, frame:preprocess(got), frame:preprocess(want), options);
end

function UnitTests:preprocess_equals_preprocess_many(prefix1, suffix1, prefix2, suffix2, cases, options)
	local frame, got_case, want_case = self.frame;
    for _, case in ipairs(cases) do
    	got_case = concat{prefix1, case[1], suffix1};
    	want_case = concat{prefix2, case[2] or case[1], suffix2};
        self:equals(got_case, frame:preprocess(got_case), frame:preprocess(want_case), options);
    end
    return self;
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) or getmetatable(t2);
    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) == 'table' then
        local mt = getmetatable(v);
        if mt and mt.__tostring then
            return tostring(v);
        end
        local result = {};
        for key, val in pairs(v) do
            if type(key) == 'number' then
                insert(result, serialize(val));
            else
                insert(result, concat{'[', serialize(key), '] = ', serialize(val)})
            end
        end
        return concat{'{', concat(result, ','), '}'};
    elseif type(v) == 'string' then
        return string.format("%q", string.gsub(v, '\n', '\\n'));
    else
        return tostring(v);
    end
end

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

function UnitTests:run(frame)
    local header = '{| class="wikitable unittests"\n! !! Text !! Expected !! Actual';
    tick, cross = frame:preprocess('{{tick}}'), frame:preprocess('{{cross}}');
    self.results, self.frame, self.differs_at = {}, frame, frame.args.differs_at;
    self.failures = 0;
    
    if self.differs_at then header = header .. ' !! Differs at'; end
    for i,test in ipairs(self.tests) do
        insert(self.results, concat{ header, "\n|+ '''", test, "''':" });
        self[test](self, frame);
        insert(self.results, "|}\n");
    end
    insert(self.results, 1, string.format(
        '<span style="color:%s; font-weight:bold;">%s</span>\n',
        self.failures == 0 and "#008000" or "#800000",
        self.failures == 0 and 'All tests passed.' or string.format('%d tests failed.', self.failures)
    ));
    self.results = concat(self.results, '\n');
    return self.results;
end

function UnitTests:__newindex(k, v)
    if type(v) == 'function' then
        table.insert(self.tests, k);
    end
    rawset(self, k, v);
end

UnitTests.__index = UnitTests;

function UnitTests:new()
    local o = { tests = {} };
    function o.run_tests(frame) return o:run(frame) end;
    return setmetatable(o, self);
end

local p = UnitTests:new();
return p