diff options
Diffstat (limited to 'www/wiki/extensions/Scribunto/tests/phpunit/engines/LuaCommon/TestFramework.lua')
-rw-r--r-- | www/wiki/extensions/Scribunto/tests/phpunit/engines/LuaCommon/TestFramework.lua | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/www/wiki/extensions/Scribunto/tests/phpunit/engines/LuaCommon/TestFramework.lua b/www/wiki/extensions/Scribunto/tests/phpunit/engines/LuaCommon/TestFramework.lua new file mode 100644 index 00000000..6231695a --- /dev/null +++ b/www/wiki/extensions/Scribunto/tests/phpunit/engines/LuaCommon/TestFramework.lua @@ -0,0 +1,246 @@ +local testframework = testframework or {} + +-- Return a string represetation of a value, including the deep structure of a table +local function deepToString( val, indent, done ) + done = done or {} + indent = indent or 0 + + local tp = type( val ) + if tp == 'string' then + return string.format( "%q", val ) + elseif tp == 'table' then + if done[val] then return '{ ... }' end + done[val] = true + local sb = { '{\n' } + local donekeys = {} + for key, value in ipairs( val ) do + donekeys[key] = true + sb[#sb + 1] = string.rep( " ", indent + 2 ) + sb[#sb + 1] = deepToString( value, indent + 2, done ) + sb[#sb + 1] = ",\n" + end + local keys = {} + for key in pairs( val ) do + if not donekeys[key] then + keys[#keys + 1] = key + end + end + table.sort( keys ) + for i = 1, #keys do + local key = keys[i] + sb[#sb + 1] = string.rep( " ", indent + 2 ) + if type( key ) == 'table' then + sb[#sb + 1] = '[{ ... }] = ' + else + sb[#sb + 1] = '[' + sb[#sb + 1] = deepToString( key, indent + 3, done ) + sb[#sb + 1] = '] = ' + end + sb[#sb + 1] = deepToString( val[key], indent + 2, done ) + sb[#sb + 1] = ",\n" + end + sb[#sb + 1] = string.rep( " ", indent ) + sb[#sb + 1] = "}" + return table.concat( sb ) + else + return tostring( val ) + end +end +testframework.deepToString = deepToString + +-- Test whether two objects are equal, including the deep structure of a table. +-- Returns 4 values: +-- boolean equal? +-- list key path to first inequality +-- mixed value from 'a' for key path +-- mixed value from 'b' for key path +local function deepEquals( a, b, keypath, done ) + -- Simple equality + if a == b then + return true + end + + keypath = keypath or {} + done = done or {} + + -- Must be equal types to be equal + local tp = type( a ) + if type( b ) ~= tp then + return false, keypath, a, b + end + + -- Special tests for certain types + + if tp == 'number' then + -- For test framework purposes, NaNs are equivalent. Lua has no + -- standard "isNaN" function, but only NaN will return true for + -- "x ~= x". + if a ~= a and b ~= b then + return true + end + + return false, keypath, a, b + end + + if tp == 'table' then + -- To avoid recursion, see if we've seen this pair of tables before. If + -- so, they must be equal or the test would have failed the first time we saw them. + done[a] = done[a] or {} + done[b] = done[b] or {} + if done[a][b] or done[b][a] then + return true + end + + -- Not seen before, record them and compare key by key. + done[a][b] = true + + local n = #keypath + 1 + -- First, check if the values for all keys in 'a' are equal in 'b'. + for k in pairs( a ) do + keypath[n] = k + local ok, kp, aa, bb = deepEquals( a[k], b[k], keypath, done ) + if not ok then + return false, kp, aa, bb + end + end + keypath[n] = nil + + -- Then check if there are any keys in 'b' that don't exist in 'a'. + for k, v in pairs( b ) do + if a[k] == nil then + keypath[n] = k + return false, keypath, nil, v + end + end + + -- Ok, all keys equal so it must match. + return true + end + + -- Ok, they're not equal + return false, keypath, a, b +end +testframework.deepEquals = deepEquals + +-- Skip a test (throws an error) +function testframework.markTestSkipped( message ) + error( 'SKIP: ' .. message, 0 ) +end + +---- Test types available --- +-- Each type has a formatter and an executor: +-- Formatters take 1 arg: expected return value from the function. +-- Executors take 2 args: function and arguments. +-- Both return a string. The test passes if the two strings match. +testframework.types = testframework.types or {} + +-- Execute a function and assert expected results +-- Expected value is a list of return values, or a string error message +testframework.types.Normal = { + format = function ( expect ) + if type( expect ) == 'string' then + return 'ERROR: ' .. expect + else + return deepToString( expect ) + end + end, + exec = function ( func, args ) + local got = { pcall( func, unpack( args ) ) } + if table.remove( got, 1 ) then + return deepToString( got ) + else + if string.sub( got[1], 1, 6 ) == 'SKIP: ' then + error( got[1], 0 ) + end + got = string.gsub( got[1], '^%S+:%d+: ', '' ) + return 'ERROR: ' .. got + end + end +} + +-- Execute an iterator-returning function and assert expected results from each +-- iteration. +-- Expected value is a list of return value lists. +testframework.types.Iterator = { + format = function ( expect ) + local sb = {} + for i = 1, #expect do + sb[i] = '[iteration ' .. i .. ']:\n' .. deepToString( expect[i] ) + end + return table.concat( sb, '\n\n' ) + end, + exec = function ( func, args ) + local sb = {} + local i = 0 + local f, s, var = func( unpack( args ) ) + while true do + local got = { f( s, var ) } + var = got[1] + if var == nil then break end + i = i + 1 + sb[i] = '[iteration ' .. i .. ']:\n' .. deepToString( got ) + end + return table.concat( sb, '\n\n' ) + end +} + +-- Execute a function and assert expected results +-- Expected value is a list of return values, or a string error message +testframework.types.ToString = { + format = function ( expect ) + if type( expect ) == 'string' then + return 'ERROR: ' .. expect + else + local ret = {} + for k, v in pairs( expect ) do + ret[k] = tostring( v ) + end + return deepToString( ret ) + end + end, + exec = function ( func, args ) + local got = { pcall( func, unpack( args ) ) } + if table.remove( got, 1 ) then + for k, v in pairs( got ) do + got[k] = tostring( v ) + end + return deepToString( got ) + else + if string.sub( got[1], 1, 6 ) == 'SKIP: ' then + error( got[1], 0 ) + end + got = string.gsub( got[1], '^%S+:%d+: ', '' ) + return 'ERROR: ' .. got + end + end +} + +-- This takes a list of tests to run, and returns the object used by PHP to +-- call them. +-- +-- Each test is a table with the following keys: +-- name: Name of the test +-- expect: Table of results expected +-- func: Function to execute +-- args: (optional) Table of args to be unpacked and passed to the function +-- type: (optional) Formatter/Executor name, default "Normal" +function testframework.getTestProvider( tests ) + return { + count = #tests, + + provide = function ( n ) + local t = tests[n] + return n, t.name, testframework.types[t.type or 'Normal'].format( t.expect ) + end, + + run = function ( n ) + local t = tests[n] + if not t then + return 'Test ' .. name .. ' does not exist' + end + return testframework.types[t.type or 'Normal'].exec( t.func, t.args or {} ) + end, + } +end + +return testframework |