--[[----------------------------------------------------------------------------

KSTestUtils.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2010 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

------------------------------------------------------------------------------]]

	-- wichitafoundation.core.table
local WFTableArchiving = require 'WFTableArchiving'

	-- wichitafoundation.core.utils
local WFArguments = require 'WFArguments'
local KSTableUtils = require 'KSTableUtils'


--============================================================================--

-- @mark KSTestUtils namespace

--[[!
	\class KSTestUtils

	\brief This namespace provides utilities for building testers.
]]--

local KSTestUtils = {}

--------------------------------------------------------------------------------

local select = select

--------------------------------------------------------------------------------

local function packn( ... )

	return { n = select( '#', ... ), ... }

end

--------------------------------------------------------------------------------

local function unpackn( t )

	return unpack( t, 1, t.n or #t )

end

--------------------------------------------------------------------------------

local function testResultsHelper( results, success, ... )

	if not success then
		return false, "Function failed"
	end
	
	local count = select( '#', ... )
	
	if count ~= results.n then
		return false, "Wrong number of results: Expected " .. tostring( results.n ) .. " got " .. tostring( count )
	end
	
	for i = 1, count do
		if results[ i ] ~= select( i, ... ) then
			return false, "Result mismatch at position " .. tostring( i )
		end
	end
	
	return true

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns a function that verifies that calls \c fn and verifies that
		it returns the expected results without raising a Lua error.
	
	\param fn (function) The function to call.
	\param ... The expected results from that function.

	\return (function) Wrapper function that returns true if \c fn( ... )
		behaves as expected.
]]--

function KSTestUtils.testResults( --[[function]] fn, ... )

	local args = packn( ... )
	
	return function( ... )
	
		return testResultsHelper( packn( ... ), pcall( fn, unpackn( args ) ) )
	
	end

end

--------------------------------------------------------------------------------
--[[!
	\brief Calls a given function and verifies that it throws a Lua error.

	\param fn (function) The function to call.
	\param ... The arguments to pass to that function.

	\return (boolean) \c false if the function does not throw an error.
]]--

function KSTestUtils.testFails( --[[function]] fn, ... )

	return pcall( fn, ... ) == false

end

--------------------------------------------------------------------------------
--[[!
	\brief Calls \c :testFails() and reports an error if it returns \c false.

	\param fn (function) The function to call.
	\param ... The arguments to pass to \c fn.
]]--

function KSTestUtils.assertFails( --[[function]] fn, ... )

	if pcall( fn, ... ) ~= false then
		assert( false, "Expected failure: " .. debug.traceback() )
	end

end

--------------------------------------------------------------------------------
--[[!
	\brief Calls a function on a set of test cases and verifies that the function
		returns expected results for each of them.
	
	Raises a Lua error if any run of the function does not return the expected
	results.

	\param fn (function) The function to call.

	\param testCases (table) A table in which each entry is a test case
		in one of the following forms:
		<code>
		{ "name", { ... args ... (including n) }, false } -- fails
		{ "name", { ... args ... (including n) }, { ... results ... (including n) } }
		</code>

	\sa KSTestUtils.functionTestCase()

	\return (what?)
]]--

function KSTestUtils.runFunctionTestCases( --[[function]] fn,
										   --[[table]] testCases )

	for i, testCase in ipairs( testCases ) do

		if testCase[ 3 ] == false then
			assert( KSTestUtils.testFails( fn, unpackn( testCase[ 2 ] ) ),
				"Test should have failed: " .. testCase[ 1 ] )
		else
			assert( KSTestUtils.testResults( fn, unpackn( testCase[ 2 ] ) )( unpackn( testCase[ 3 ] ) ),
				"Test failed: " .. testCase[ 1 ] )
		end

	end

end

--------------------------------------------------------------------------------

KSTestUtils.Failure = {}

--------------------------------------------------------------------------------
--[[!
	\brief Utility function for building a test case.

	\param name (string) The name of this test case.
		
	\return (function)
		If the results are \c KSTestUtils.Failure
		then we mark this as a failure. (in the log?)

	\todo I'm not sure how this is used. Can someone find an example and
		document this better? [scouten 2013-02-11]
]]--

function KSTestUtils.functionTestCase( --[[ string]] name )

	return function( ... )

		local args = packn( ... )

		return function( ... )

			local results
			if ( ... ) == KSTestUtils.Failure then
				results = false
			else
				results = packn( ... )
			end

			return { name, args, results }

		end

	end

end

--------------------------------------------------------------------------------

local function assertMatchValues( x, y, msg )

	if x == y then return end

	local errorMsg = string.format( "%s ~= %s", tostring( x ), tostring( y ) )
	if msg then
		errorMsg = string.format( "%s (%s)", errorMsg, tostring( msg ) )
	end

	errorMsg = errorMsg .. ": \n" .. debug.traceback()

	error( errorMsg )

end

--------------------------------------------------------------------------------
--[[!
	\brief Reports whether a set of arguments match.

	\param ... The argument sets. For example:
		<code>assertMatchArgs( 'data', 3 )( ... )</code>
		checks whether the first set of 3 arguments matches the second
		set of 3. ((is this even close to how it works??))
	 	
	 \return If the first set does not match the second set, throws an execption
		and generates debug output describing the mismatch.
]]--

function KSTestUtils.assertMatchArgs( ... )

	local n = select( '#', ... )

	local expected = { ... }
	
	return function( ... )

		assertMatchValues( n, select( '#', ... ), "argument count" )

		for i = 1, n do
			assertMatchValues( expected[ i ], select( i, ... ), "argument " .. tostring( i ) )
		end

	end

end

--------------------------------------------------------------------------------
--[[!
	\brief Reports whether two tables match.
	
	\param actual (table) The table to check.
	\param expected (table)	The table to match against.

	\return If the first table does not match the second, throws an execption
		and generates debug output describing the mismatch.
]]--

function KSTestUtils.assertTableMatches( --[[table]] actual,
										 --[[table]] expected )

	WFArguments.check( 'table', 'table' )

	if not KSTableUtils.deepEquals( actual, expected, 20, true ) then

		debug.print( "\nactual: ", WFTableArchiving.tableToString( actual ), "\n" )
		debug.print( "\nexpected: ", WFTableArchiving.tableToString( expected ), "\n" )
		
		error( "actual results table did not match expected table", 2 )
	
	end

end

--------------------------------------------------------------------------------

local function toStringAll( sep, ... )

	local n = select( '#', ... )
	local t = { ... }
	
	local tAsStrings = {}
	
	for i = 1, n do
		tAsStrings[ i ] = tostring( t[ i ] )
	end
	
	return table.concat( tAsStrings, sep )

end

--------------------------------------------------------------------------------
--[[!
	\brief Reports whether two sets of arguments are equal.
	
	This function returns a function which should be called with two sets of
	arguments, as follows:
	
		<code>assertEquals( msg )( ... )( ... )</code>
	
	If the first ... does not match the second ..., then we throw \c msg
	as a Lua error and generate debug output describing the mismatch.
	
	\param test (optional, WFTestCase) A generic test-case object.
	\param msg (string) The error message to throw on mismatch.

	\return (function) function to call with first and second argument sets
]]--

function KSTestUtils.assertEquals( --[[optional WFTestCase]] test,
								   --[[string]] msg )

	-- Sliding first parameter is a bit ugly, but it reads better at
	-- call sites if we use it this way.

	if type( test ) == 'string' and not msg then
		msg = test
		test = nil
	end

	return function( ... )
	
		local n = select( '#', ... )
		local t = { ... }
		
		local function equals( ... )
		
			if n ~= select( '#', ... ) then
				return false, "length mismatch"
			end
			
			for i = 1, n do
				if t[ i ] ~= select( i, ... ) then
					return false, ( "value mismatch at " .. tostring( i ) )
				end
			end
			
			return true
		
		end
		
		return function( ... )
		
			local eq, reason = equals( ... )
			
			if not eq then
			
				if not eq then

					if test then
						test:log( "Failed to match: " .. tostring( msg ) )
						test:log( "\nExpected:\n - " .. toStringAll( '\n - ', unpack( t, 1, n ) ) )
						test:log( "\nActual:\n - " .. toStringAll( '\n - ', ... ) )
					end

					debug.print( "Failed to match: ", msg )
					debug.print( "Expected: ", unpack( t, 1, n ) )
					debug.print( "Actual: ", ... )

				end
			
				error( msg .. ": " .. reason )

			end
		
		end
		
	end

end

--------------------------------------------------------------------------------

return KSTestUtils
