--[[ 
        luaunit.lua

Description: A unit testing framework
Homepage: http://phil.freehackers.org/luaunit/
Initial author: Ryu, Gwang (http://www.gpgstudy.com/gpgiki/LuaUnit)
Lot of improvements by Philippe Fremy <phil@freehackers.org>
Version: 1.3
License: X11 License, see LICENSE.txt

Changes between 1.3 and 1.2a:
- port to lua 5.1
- use orderedPairs() to iterate over a table in the right order
- change the order of expected, actual in assertEquals() and the default value of
  USE_EXPECTED_ACTUAL_IN_ASSERT_EQUALS. This can be adjusted with
  USE_EXPECTED_ACTUAL_IN_ASSERT_EQUALS.

Changes between 1.2a and 1.2:
- fix: test classes were not run in the right order

Changes between 1.2 and 1.1:
- tests are now run in alphabetical order
- fix a bug that would prevent all tests from being run

Changes between 1.1 and 1.0:
- internal variables are not global anymore
- you can choose between assertEquals( actual, expected) or assertEquals(
  expected, actual )
- you can assert for an error: assertError( f, a, b ) will assert that calling
  the function f(a,b) generates an error
- display the calling stack when an error is spotted
- a dedicated class collects and displays the result, to provide easy
  customisation
- two verbosity level, like in python unittest
]]--

argv = arg

--[[ Some people like assertEquals( actual, expected ) and some people prefer 
assertEquals( expected, actual ).

]]--
USE_EXPECTED_ACTUAL_IN_ASSERT_EQUALS = true

function assertError(f, ...)
    -- assert that calling f with the arguments will raise an error
    -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
    local has_error, error_msg = not pcall( f, ... )
    if has_error then return end 
    error( "No error generated", 2 )
end

function assertEquals(actual, expected)
    -- assert that two values are equal and calls error else
    if  actual ~= expected  then
        local function wrapValue( v )
            if type(v) == 'string' then return "'"..v.."'" end
            return tostring(v)
        end
        if not USE_EXPECTED_ACTUAL_IN_ASSERT_EQUALS then
            expected, actual = actual, expected
        end

        local errorMsg
        if type(expected) == 'string' then
            errorMsg = "\nexpected: "..wrapValue(expected).."\n"..
                             "actual  : "..wrapValue(actual).."\n"
        else
            errorMsg = "expected: "..wrapValue(expected)..", actual: "..wrapValue(actual)
        end
        print (errorMsg)
        error( errorMsg, 2 )
    end
end

assert_equals = assertEquals
assert_error = assertError

function wrapFunctions(...)
    -- Use me to wrap a set of functions into a Runnable test class:
    -- TestToto = wrapFunctions( f1, f2, f3, f3, f5 )
    -- Now, TestToto will be picked up by LuaUnit:run()
    local testClass, testFunction
    testClass = {}
    local function storeAsMethod(idx, testName)
        testFunction = _G[testName]
        testClass[testName] = testFunction
    end
    table.foreachi( {...}, storeAsMethod )
    return testClass
end

function __genOrderedIndex( t )
    local orderedIndex = {}
    for key,_ in pairs(t) do
        table.insert( orderedIndex, key )
    end
    table.sort( orderedIndex )
    return orderedIndex
end

function orderedNext(t, state)
    -- Equivalent of the next() function of table iteration, but returns the
    -- keys in the alphabetic order. We use a temporary ordered key table that
    -- is stored in the table being iterated.

    --print("orderedNext: state = "..tostring(state) )
    if state == nil then
        -- the first time, generate the index
        t.__orderedIndex = __genOrderedIndex( t )
        key = t.__orderedIndex[1]
        return key, t[key]
    end
    -- fetch the next value
    key = nil
    for i = 1,#t.__orderedIndex do
        if t.__orderedIndex[i] == state then
            key = t.__orderedIndex[i+1]
        end
    end

    if key then
        return key, t[key]
    end

    -- no more value to return, cleanup
    t.__orderedIndex = nil
    return
end

function orderedPairs(t)
    -- Equivalent of the pairs() function on tables. Allows to iterate
    -- in order
    return orderedNext, t, nil
end

-------------------------------------------------------------------------------
UnitResult = { -- class
    failureCount = 0,
    testCount = 0,
    errorList = {},
    currentClassName = "",
    currentTestName = "",
    testHasFailure = false,
    verbosity = 1
}
    function UnitResult:displayClassName()
        print( '>>>>>>>>> '.. self.currentClassName )
    end

    function UnitResult:displayTestName()
        if self.verbosity > 0 then
            print( ">>> ".. self.currentTestName )
        end
    end

    function UnitResult:displayFailure( errorMsg )
        if self.verbosity == 0 then
            io.stdout:write("F")
        else
            print( errorMsg )
            print( 'Failed' )
        end
    end

    function UnitResult:displaySuccess()
        if self.verbosity > 0 then
            --print ("Ok" )
        else 
            io.stdout:write(".")
        end
    end

    function UnitResult:displayOneFailedTest( failure )
        testName, errorMsg = unpack( failure )
        print(">>> "..testName.." failed")
        print( errorMsg )
    end

    function UnitResult:displayFailedTests()
        if #self.errorList == 0 then return end
        print("Failed tests:")
        print("-------------")
        table.foreachi( self.errorList, self.displayOneFailedTest )
        print()
    end

    function UnitResult:displayFinalResult()
        print("=========================================================")
        self:displayFailedTests()
        local failurePercent, successCount
        if self.testCount == 0 then
            failurePercent = 0
        else
            failurePercent = 100 * self.failureCount / self.testCount
        end
        successCount = self.testCount - self.failureCount
        print( string.format("Success : %d%% - %d / %d",
            100-math.ceil(failurePercent), successCount, self.testCount) )
        return self.failureCount
    end

    function UnitResult:startClass(className)
        self.currentClassName = className
        self:displayClassName()
    end

    function UnitResult:startTest(testName)
        self.currentTestName = testName
        self:displayTestName()
        self.testCount = self.testCount + 1
        self.testHasFailure = false
    end

    function UnitResult:addFailure( errorMsg )
        self.failureCount = self.failureCount + 1
        self.testHasFailure = true
        table.insert( self.errorList, { self.currentTestName, errorMsg } )
        self:displayFailure( errorMsg )
    end

    function UnitResult:endTest()
        if not self.testHasFailure then
            self:displaySuccess()
        end
    end

-- class UnitResult end


LuaUnit = {
    result = UnitResult
}
    -- Split text into a list consisting of the strings in text,
    -- separated by strings matching delimiter (which may be a pattern). 
    -- example: strsplit(",%s*", "Anna, Bob, Charlie,Dolores")
    function LuaUnit.strsplit(delimiter, text)
        local list = {}
        local pos = 1
        if string.find("", delimiter, 1) then -- this would result in endless loops
            error("delimiter matches empty string!")
        end
        while 1 do
            local first, last = string.find(text, delimiter, pos)
            if first then -- found?
                table.insert(list, string.sub(text, pos, first-1))
                pos = last+1
            else
                table.insert(list, string.sub(text, pos))
                break
            end
        end
        return list
    end

    function LuaUnit.isFunction(aObject) 
        return 'function' == type(aObject)
    end

    function LuaUnit.strip_luaunit_stack(stack_trace)
        stack_list = LuaUnit.strsplit( "\n", stack_trace )
        strip_end = nil
        for i = #stack_list,1,-1 do
            -- a bit rude but it works !
            if string.find(stack_list[i],"[C]: in function `xpcall'",0,true)
                then
                strip_end = i - 2
            end
        end
        if strip_end then
            table.setn( stack_list, strip_end )
        end
        stack_trace = table.concat( stack_list, "\n" )
        return stack_trace
    end

    function LuaUnit:runTestMethod(aName, aClassInstance, aMethod)
        local ok, errorMsg
        -- example: runTestMethod( 'TestToto:test1', TestToto, TestToto.testToto(self) )
        LuaUnit.result:startTest(aName)

        -- run setUp first(if any)
        if self.isFunction( aClassInstance.setUp) then
                aClassInstance:setUp()
        end

        local function err_handler(e)
            return e..'\n'..debug.traceback()
        end

        -- run testMethod()
        ok, errorMsg = xpcall( aMethod, err_handler )
        if not ok then
            errorMsg  = self.strip_luaunit_stack(errorMsg)
            LuaUnit.result:addFailure( errorMsg )
        end

        -- lastly, run tearDown(if any)
        if self.isFunction(aClassInstance.tearDown) then
             aClassInstance:tearDown()
        end

        self.result:endTest()
    end

    function LuaUnit:runTestMethodName( methodName, classInstance )
        -- example: runTestMethodName( 'TestToto:testToto', TestToto )
        local methodInstance = loadstring(methodName .. '()')
        LuaUnit:runTestMethod(methodName, classInstance, methodInstance)
    end

    function LuaUnit:runTestClassByName( aClassName )
        -- example: runTestMethodName( 'TestToto' )
        local hasMethod, methodName, classInstance
        hasMethod = string.find(aClassName, ':' )
        if hasMethod then
            methodName = string.sub(aClassName, hasMethod+1)
            aClassName = string.sub(aClassName,1,hasMethod-1)
        end
        classInstance = _G[aClassName]
        if not classInstance then
            error( "No such class: "..aClassName )
        end

        LuaUnit.result:startClass( aClassName )

        if hasMethod then
            if not classInstance[ methodName ] then
                error( "No such method: "..methodName )
            end
            LuaUnit:runTestMethodName( aClassName..':'.. methodName, classInstance )
        else
            -- run all test methods of the class
            for methodName, method in orderedPairs(classInstance) do
            --for methodName, method in classInstance do
                if LuaUnit.isFunction(method) and string.sub(methodName, 1, 4) == "test" then
                    LuaUnit:runTestMethodName( aClassName..':'.. methodName, classInstance )
                end
            end
        end
        print()
    end

    function LuaUnit:run(...)
        -- Run some specific test classes.
        -- If no arguments are passed, run the class names specified on the
        -- command line. If no class name is specified on the command line
        -- run all classes whose name starts with 'Test'
        --
        -- If arguments are passed, they must be strings of the class names 
        -- that you want to run
                args={...};
        if #args > 0 then
            table.foreachi( args, LuaUnit.runTestClassByName )
        else 
            if argv and #argv > 0 then
                table.foreachi(argv, LuaUnit.runTestClassByName )
            else
                -- create the list before. If you do not do it now, you
                -- get undefined result because you modify _G while iterating
                -- over it.
                testClassList = {}
                for key, val in pairs(_G) do 
                    if string.sub(key,1,4) == 'Test' then 
                        table.insert( testClassList, key )
                    end
                end
                for i, val in orderedPairs(testClassList) do 
                        LuaUnit:runTestClassByName(val)
                end
            end
        end
        return LuaUnit.result:displayFinalResult()
    end
-- class LuaUnit