--[[ 
        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

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE X
CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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,table.getn(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 table.getn( 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 = table.getn(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(args)
        -- 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

        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

