summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Scribunto/tests/phpunit/engines/LuaCommon/TestFramework.lua
diff options
context:
space:
mode:
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.lua246
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