--[[ 
		luaunit.lua

Description: A unit testing framework
Initial author: Ryu, Gwang (http://www.gpgstudy.com/gpgiki/LuaUnit)
improvements by Philippe Fremy <phil@freehackers.org>
Version: 1.1 

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

REVERSED_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
	has_error, error_msg = not pcall( f, unpack(arg) )
	if has_error then return end 
	error( "No error generated", 2 )
end

function assertEquals(expected, actual)
	-- 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 REVERSED_ASSERT_EQUALS then
			expected, actual = actual, expected
		end

		local errorMsg = "expected: "..wrapValue(expected)..", actual: "..wrapValue(actual)
		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( arg, storeAsMethod )
	return testClass
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) )
    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 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
		if arg.n > 0 then
			table.foreachi( arg, LuaUnit.runTestClassByName )
		else 
			if argv and argv.n > 0 then
				table.foreachi(argv, LuaUnit.runTestClassByName )
			else
				for var, value in _G do
					if string.sub(var,1,4) == 'Test' then 
						LuaUnit:runTestClassByName(var)
					end
				end
			end
		end
		LuaUnit.result:displayFinalResult()
	end
-- class LuaUnit

