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

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

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

local T = { }

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

local assertf = require 'KSLuaUtils'.assertf

local WFArguments = require 'WFArguments'

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

local function checkMetaForMode( t, mode )

	assert( type( t ) == 'table', "Expected table" )

	local mt = debug.getmetatable( t )
	
	assert( mt, "No metatable" )
	
	assertf( mt.__mode == mode,
		    "Wrong mode in metatable. Expected %s got %s.",
			mode, mt.__mode )

end

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

function T.testMakeWithMode()

	local TU = require 'KSTableUtils'
	
	for _, mode in ipairs( { 'k', 'v', 'kv' } ) do
		checkMetaForMode( TU.makeWithMode( mode ), mode )
	end
	
	assert( debug.getmetatable( TU.makeWithMode() ) == nil,
		"Unexpected metatable in no parameter case" )
		
	assert( debug.getmetatable( TU.makeWithMode( '' ) ) == nil,
		"Unexpected metatable in no empty string case" )
		
	assert( pcall( TU.makeWithMode, 'foo' ) == false,
		"Expected failure for bad mode string" )
		
	assert( pcall( TU.makeWithMode, { } ) == false,
		"Expected failure for non-string mode" )
		
end

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

function T.testMakeFullyWeak()

	local TU = require 'KSTableUtils'
	
	checkMetaForMode( TU.makeFullyWeak(), 'kv' )
	
	assert( TU.makeFullyWeak() ~= TU.makeFullyWeak(), "Unexpected table recycling" )
		
end

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

function T.testMake()

	local TU = require 'KSTableUtils'
	
	local t = TU.make()
	
	assert( type( t ) == 'table', "make should produce a table" )
	
	assert( debug.getmetatable( t ) == nil, "That table should not have a metatable" )
	
	assert( TU.make() ~= TU.make(), "Unexpected table recycling" )
		
end

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

function T.testMakeWithMetatable()

	local TU = require 'KSTableUtils'
	
	local mt = { }
	
	local t = TU.makeWithMetatable( mt )
	
	assert( type( t ) == 'table', "makeWithMetatable should produce a table" )
	
	assert( debug.getmetatable( t ) == mt, "That table should have the correct metatable" )
	
	assert( TU.makeWithMetatable( mt ) ~= TU.makeWithMetatable( mt ), "Unexpected table recycling" )
	
	assert( pcall( TU.makeWithMetatable, 'foo' ) == false,
		"makeWithMetatable should reject non tables as metatables" )
		
end

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

function T.testMakeFactoryWithMetatable()

	local TU = require 'KSTableUtils'
	
	local mt = { }
	
	local factory = TU.makeFactoryWithMetatable( mt )
	
	-- We don't test that factory is a function because what we really care
	-- about is that it is callable which we test by calling it.
	
	local t = factory()
	
	assert( type( t ) == 'table', "makeWithMetatable should produce a table" )
	
	assert( debug.getmetatable( t ) == mt, "That table should have the correct metatable" )
	
	assert( factory() ~= factory(), "Unexpected table recycling" )
	
	assert( pcall( TU.makeFactoryWithMetatable, 'foo' ) == false,
		"makeFactoryWithMetatable should reject non tables as metatables" )
		
	factory = TU.makeFactoryWithMetatable( nil )
	
	assert( factory, "makeWithMetatable( nil ) should be same as make()" )
		
end

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

function T.testMakeWrapWithMetatable()

	local TU = require 'KSTableUtils'
	
	local mt = { }
	
	local wrapper = TU.makeWrapWithMetatable( mt )
	
	-- We don't test that wrapper is a function because what we really care
	-- about is that it is callable which we test by calling it.
	
	local t = { }
	
	local tt = wrapper( t )
	
	assert( t == tt, "wrapper should not change table identity" )
	
	assert( debug.getmetatable( t ) == mt, "That table should have the correct metatable" )
	
	local mt2 = { }
	
	local wrapper2 = TU.makeWrapWithMetatable( mt2 )
	
	local t2 = { }
	
	local tt2 = wrapper2( t2 )
	
	assert( t2 == tt2, "wrapper should not change table identity (2)" )
	
	assert( debug.getmetatable( t2 ) == mt2, "That table should have the correct metatable (2)" )
	
	local t3 = { }
	
	local tt3 = wrapper( t3 )
	
	assert( t3 == tt3, "wrapper should not change table identity (3)" )
	
	assert( debug.getmetatable( t3 ) == mt, "That table should have the correct metatable (3)" )
	
	assert( pcall( TU.makeWrapWithMetatable, 'foo' ) == false,
		"makeWrapWithMetatable should reject non tables as metatables" )
		
	assert( pcall( TU.makeWrapWithMetatable, nil ) == false,
		"makeWrapWithMetatable should reject nil metatables" )
		
end

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

function T.testMakeReadOnly()

	local TU = require 'KSTableUtils'

	local makeReadOnly = assert( TU.makeReadOnly )
	
	for _, v in ipairs{ 1, "a", true, false } do
		assert( makeReadOnly( v ) == v, "makeReadOnly should preserve non-table values" )
	end
	
	assert( makeReadOnly( nil ) == nil, "makeReadOnly should preserve nil" )
	
	local t = { foo = 42, baz = 'hello' }
	
	local tt = makeReadOnly( t )
	
	for k, v in pairs( t ) do
		assert( tt[ k ] == v, "read-only table should match source values" )
	end
	
	for k in TU.readOnlyPairs( tt ) do
		assert( t[ k ] ~= nil, "read-only table should have only the same values as the source" )
	end
	
	assert( pcall( function() tt.x = 9 end ) == false, "read-only table should reject writes" )
	
	assert( t.x == nil, "Write should not have changed base table." )
	
	assert( tt.x == nil, "Write should not have changed read-only table." )
	
	assert( pcall( function() tt.foo = 54 end ) == false, "read-only table should reject writes to existing fields" )
	
	assert( t.foo == 42, "Write should not have changed base table." )
	
	assert( tt.foo == 42, "Write should not have changed read-only table." )
	
	assert( pcall( function() t.foo = 54 end ) == true, "Write to base table should succeed" )
	
	assert( tt.foo == 54, "Write to base table should be visible in read-only table" )
	
	assert( tt == makeReadOnly( t ), "Wrappers should be shared" )

end

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

function T.testMapArrayToSetIdentityCase()

	local TU = require 'KSTableUtils'
	
	assert( TU.deepEquals(
				TU.mapArrayToSet{ foo = true, 'a', 'b', false, 5 },
				{ a = 'a', b = 'b', [ false ] = true, [ 5 ] = 5 } ),
			"Incorrect set computed" )

end

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

function T.testMapArrayToSetFieldExtractorCase()

	local TU = require 'KSTableUtils'
	local FU = require 'KSFunctionUtils'
	
	local mapped = TU.mapArrayToSet(
		FU.fieldExtractorFn( 'id' ),
		{ { id = 'a' }, { id = 'b' }, { id = false }, { no_id = true }, { id = 5 } } )
	
	assert( TU.deepEquals(
				mapped,
				{ a = 'a', b = 'b', [ false ] = true, [ 5 ] = 5 } ),
			"Incorrect set computed" )

end

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

function T.testMapArrayToSetFunctionCase()

	local TU = require 'KSTableUtils'
	
	assert( TU.deepEquals(
				TU.mapArrayToSet(
					function( x ) return #x end,
					{ "cat", "dog", "cat", "bird", "gorilla" } ),
				{ [ 3 ] = 3, [ 4 ] = 4, [ 7 ] = 7 } ),
			"Incorrect set computed" )

end

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

function T.testShallowCopy()
	
	local TU = require 'KSTableUtils'
	local a = {
		foo = 'bar',
	}
	local b = TU.shallowcopy( a )
	
	assert( b.foo == 'bar', 'Basic shallow copy test' )
	
end

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

function T.testDeepEquals()

	local TU = require 'KSTableUtils'
	
	assert( TU.deepEquals( 1, 1 ))
	assert( TU.deepEquals( "", "" ))
	assert( TU.deepEquals( nil, nil ))

	local emptyTab = { }
	assert( TU.deepEquals( emptyTab, emptyTab ), "test short circuit comparison, comparing same element always true" )
	assert( TU.deepEquals( emptyTab, { } ), "empty table should match another empty table" )
	
	assert( not TU.deepEquals( 1, 2 ) )
	assert( not TU.deepEquals( { }, nil ))
	assert( not TU.deepEquals( { }, 42 ))
	assert( not TU.deepEquals( { }, {{ }} ))
	
	assert( 	TU.deepEquals( { a = 42 }, { a = 42 } ) )
	assert( not TU.deepEquals( { a = 42 }, { a = 43 } ), "not equal key value is different" )
	assert( not TU.deepEquals( { a = 42 }, { b = 42 } ), "not equal, key is different" )
	assert( not TU.deepEquals( { a = { b = 42 } }, { a = 42 }), "not equal, type mismatch" )
	
	local function testFunc1() return 1 end
	local function testFunc2() return 2 end
	
	local tab1 = { nil, { }, true, false, testFunc1, 41, mol = 42, "cat", { "dog" }, {{ "cat", "bird" }}, {{{ "gorilla", testFunc2 }}, a = "fish"} }
	local tab2 = { nil, { }, true, false, testFunc1, 41, mol = 42, "cat", { "dog" }, {{ "cat", "bird" }}, {{{ "gorilla", testFunc2 }}, a = "fish"} }
	local tabf = { nil, { }, true, false, testFunc1, 41, mol = 42, "cat", { "dog" }, {{ "cat", "bird" }}, {{{ "gorilla", testFunc2 }}, a = "fishy"} }
	local tabn = { nil, { }, true, false, testFunc1, 41, mol = 42, "cat", { "dog" }, {{ "cat", "bird" }}, {{{ "gorilla", testFunc2 }}} }
	assert( TU.deepEquals( tab1, tab2 ) )
	assert( not TU.deepEquals( tab1, tabf), "this test should fail because it's fishy at the end" )
	assert( not TU.deepEquals( tab1, tabn), "this test should fail because it doesn't have a = fish" )

	-- test with depth limits
	assert( 	TU.deepEquals( {  }, {  }, 2 ), "empty table is considered 2 deep" ) -- seems like it is really only 1 deep, but I doubt this matters
	assert( not TU.deepEquals( {  }, {  }, 1 ), "should fail, empty table should be considered 2 deep" )
	
	assert( 	TU.deepEquals( { 42 }, { 42 }, 2 ), "a simple table is 2 deep, first level is the table itself, 2nd level is the numbers within them" )
	assert( not TU.deepEquals( { 42 }, { 42 }, 1 ) )
	
	local tripleNestedTab = {{{ mol = 42 }}}
	assert( 	TU.deepEquals( tripleNestedTab, tripleNestedTab, 1 ), "comparing the same table will be equal even if it is over the depthLimit" )
	assert( 	TU.deepEquals( {{{ mol = 42 }}}, {{{ mol = 42 }}}, 4 ), "should be equal, depth is 4 in this case" )
	assert( not TU.deepEquals( {{{ mol = 42 }}}, {{{ mol = 42 }}}, 3 ), "should not be equal, because it's over the depthLimit" )

	assert( TU.deepEquals( tab1, tab2, 5, false ) )
	assert( not TU.deepEquals( tab1, tab2, 4, false ), "not equal because depth was exceeded" )

	-- increase code coverage by using printDebugOutput true
	assert( TU.deepEquals( tab1, tab2, 5, true ) )
	assert( not TU.deepEquals( tab1, tab2, 4, true), "not equal because depth was exceeded" )
	assert( not TU.deepEquals( { a = 42 }, { a = 42, b = 43 }, 5, true), "not equal because b missing" )
	assert( not TU.deepEquals( { a = { b = 42 } }, { a = 2 }, 5, true), "not equal because a type mismatch" )

end

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

function T.testDeepCopy()

	local TU = require 'KSTableUtils'
	
	-- test simple types
	assert( TU.deepCopy( 1 ) == 1)
	assert( TU.deepCopy( "" ) == "" )
	assert( TU.deepCopy( "test" ) == "test" )
	assert( TU.deepCopy( nil ) == nil )
	
	-- test empty table
	local emptyTab = { }
	local emptyTab2 = TU.deepCopy( emptyTab )
	assert( emptyTab2 ~= emptyTab, "newly created table that is also empty so not equal" )
	assert( TU.deepEquals(emptyTab2, emptyTab), "both should be empty" )

	-- test table with variables reused within the table
	local varTab = { 1, 2, 3, 5, 7 }
	local tabWithVars = { varTab, a = varTab, b = varTab }
	local copyOfTabWithVars = TU.deepCopy( tabWithVars )
	assert( copyOfTabWithVars[1] == copyOfTabWithVars['a'] and copyOfTabWithVars[1] == copyOfTabWithVars['b'] )

	-- test table with various types of data
	local function testFunc1() return 1 end
	local function testFunc2() return 2 end
	local tab1 = { nil, { }, true, false, testFunc1, 41, mol = 42, "cat", { "dog" }, {{ "cat", "bird" }}, {{{ "gorilla", testFunc2 }}, a = "fish"} }
	local tab2 = TU.deepCopy( tab1 )
	assert( TU.deepEquals( tab1, tab2 ) )

	-- exceed max depth, currently 20
	local tabWith20Levels =
						{ a1 =	{ a2 = 	{ a3 =	{ a4 =	{ a5 =	{ a6 =	{ a7 =	{ a8 =	{ a9 =	{ a10 =
						{ a11 = { a12 = { a13 = { a14 = { a15 = { a16 = { a17 = { a18 = { a19 = { a20 = 'hi'
						}}		}}		}}		}}		}}		}}		}}		}}		}}		}}
	
	local copyOfTabWith20Levels = TU.deepCopy( tabWith20Levels )
	assert( not TU.deepEquals( copyOfTabWith20Levels, tabWith20Levels, 10 ), "specifying deepCopy's MaxLevel support at 10 levels so this will fail" )
	assert( TU.deepEquals( copyOfTabWith20Levels, tabWith20Levels, 21 ), "specifying deepCopy's MaxLevel support + 1 should work" )
	
	local tabWith21Levels = { a21 = tabWith20Levels }
	local success, copyOfTabWith21Levels = pcall( TU.deepCopy, tabWith21Levels )
	assert( not success and string.find(copyOfTabWith21Levels, "Depth limit exceeded"), "deepCopy has a limit of 20 levels" )
	
	-- tab with user data not allowed
	local KSUserdataProxy = assert( require "KSUserdataProxy" )
	local factory = KSUserdataProxy.makeFactory()
	local proxy = factory()
	assert( type(proxy) == 'userdata')

	assert( not pcall(TU.deepCopy, proxy), "not allowed to copy userdata with deepCopy" )
	
	local tabWithUserData = { a = 1, b = proxy, c = 2 }
	local cudSuccess, copyOfTabWithUserData = pcall(TU.deepCopy, tabWithUserData )
	assert( not cudSuccess and string.find(copyOfTabWithUserData, "deepCopy does not support userdata"), "deepCopy is unable to copy table a table containing user data" )
	
	-- tab with metatable not allowed
	local mt = { }
	local t = TU.makeWithMetatable( mt )
	local cmtSuccess, copyOfMetaTable = pcall(TU.deepCopy, t )
	assert( not cmtSuccess and string.find(copyOfMetaTable, "deepCopy does not support metatables"), "deepCopy does not copy tables with metatables" )

end

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

local function successfulMergeTest( name )
	return function( p )
		T[ name ] = function()
			local TU = require 'KSTableUtils'
			local dc = function( key ) return TU.deepCopy( p[ key ] ) end
			local getMergeAlgorithm = p.getMergeAlgorithm
			assert( TU.deepEquals( TU.merge( dc 'base', dc 'a', dc 'b' , getMergeAlgorithm ),
								dc 'merged', nil, true ),
				"Incorrect merge results (1)" )
			if not p.assymetric then
				assert( TU.deepEquals( TU.merge( dc 'base', dc 'b', dc 'a', getMergeAlgorithm ),
								dc 'merged', nil, true ),
					"Incorrect merge results (2)" )
				-- Also test a recursive version
				assert( TU.deepEquals( TU.merge(
							{ rec = dc 'base', rec2 = dc 'base' },
							{ rec = dc 'a', rec2 = dc 'b' },
							{ rec = dc 'b', rec2 = dc 'a' },
							p.getMergeAlgorithm ),
					{ rec = dc 'merged', rec2 = dc 'merged' }, nil, true ),
					"Recursive merge results were wrong" )
			end
		end
	end
end

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

local function failureMergeTest( name )
	return function( p )
		T[ name ] = function()
			local TU = require 'KSTableUtils'
			assert( false == pcall( TU.merge, p.base, p.a, p.b, p.getMergeAlgorithm ),
				"Expected merge to fail (1)" )
			assert( false == pcall( TU.merge, p.base, p.b, p.a, p.getMergeAlgorithm ),
				"Expected merge to fail (2)" )
		end
	end
end

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

successfulMergeTest "testSimpleMergeAB" {
	base = "foo", a = "baz", b = "baz", merged = "baz"
}

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

successfulMergeTest "testSimpleMergeBaseA" {
	base = "foo", a = "foo", b = "baz", merged = "baz"
}

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

successfulMergeTest "testTableTableAtomicMerge" {
	base = { }, a = { }, b = "foo", merged = "foo"
}

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

-- A real use would memoize the key match, but we don't bother with that
-- given that this is for unit tests.

local function getMergeAlgorithm_atomic( forPath, base, a, b )

	WFArguments.check( 'table', 'table', 'table', 'table' )
	
	local lastKey = forPath[ #forPath ]
	
	if type( lastKey ) == 'string' and string.find( lastKey, '_atomic$' ) then
		return 'atomic'
	end
	
	-- Otherwise just fall through

end

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

successfulMergeTest "testTableTableTableMerge" {
	base = {
		core = 0,
		foo = 1,
		baz = 2,
		frex = 7,
		dimple = "dent"
	},
	a = {
		core = 0,
		foo = 1,
		baz = "Hello",
		frex = 11,
		bleen = "Hmmm",
		brax = "Huh?"
	},
	b = {
		core = 0,
		foo = "Green",
		baz = 2,
		frex = 11,
		dimple = "dent",
		bleen = "Hmmm"
	},
	merged = {
		core = 0, 	-- Same in all
		foo = "Green", -- b
		baz = "Hello", -- a
		frex = 11, -- Changed in both
		bleen = "Hmmm", -- both (but new)
		-- dimple deleted in a
		brax = "Huh?", -- new in a
	}
}

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

successfulMergeTest "testMergeWithAtomic" {
	base = {
		core = 0,
		foo = 1,
		baz = 2,
		frex = 7,
		dimple = "dent",
		smash = { so = "cool" },
		spatter_atomic = { so = "cool" }
	},
	a = {
		core = 0,
		foo = 1,
		baz = "Hello",
		frex = 11,
		bleen = "Hmmm",
		brax = "Huh?",
		smash = { so = "cool", yet = "not" },
		flump_atomic = { "big", "bigger" },
		spatter_atomic = { so = "cool" }
	},
	b = {
		core = 0,
		foo = "Green",
		baz = 2,
		frex = 11,
		dimple = "dent",
		bleen = "Hmmm",
		flump_atomic = { "big", "bigger" }
	},
	merged = {
		core = 0, 	-- Same in all
		foo = "Green", -- b
		baz = "Hello", -- a
		frex = 11, -- Changed in both
		bleen = "Hmmm", -- both (but new)
		-- dimple deleted in a
		brax = "Huh?", -- new in a
		smash = { yet = "not" }, -- one key deleted in b but not both
		flump_atomic = { "big", "bigger" } -- new in both and atomic match
		-- spatter_atomic deleted in b
	},
	getMergeAlgorithm = getMergeAlgorithm_atomic
}

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

failureMergeTest "testSimpleMergeFailure" {
	base = "foo", a = "baz", b = 3
}

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

failureMergeTest "testTableTableAtomicMergeFailure" {
	base = { }, a = { entry = 'Hello' }, b = 3
}

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

failureMergeTest "testTableTableAtomicMergeFailure2" {
	base = { entry = 'Hello' }, a = { entry = 'Howdy' }, b = 3
}

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

failureMergeTest "testMergeFailureWithAtomic" {
	base = {
		core = 0,
		foo = 1,
		baz = 2,
		frex = 7,
		dimple = "dent",
		smash = { so = "cool" },
		spatter_atomic = { so = "cool" }
	},
	a = {
		core = 0,
		foo = 1,
		baz = "Hello",
		frex = 11,
		bleen = "Hmmm",
		brax = "Huh?",
		smash = { so = "cool", yet = "not" },
		flump_atomic = { "big", "bigger" },
		spatter_atomic = { so = "cool", yet = "not" } -- atomic mismatch
	},
	b = {
		core = 0,
		foo = "Green",
		baz = 2,
		frex = 11,
		dimple = "dent",
		bleen = "Hmmm",
		flump_atomic = { "big", "bigger" }
	},
	getMergeAlgorithm = getMergeAlgorithm_atomic
}

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

failureMergeTest "testMergeFailureInvalidMergeFunction" {
	base = { "foo" }, a = { "baz" }, b = { "baz" }, merged = { "baz" },
	getMergeAlgorithm = function()
		return 'invalidMergeType'
	end
}

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

function T.testInvert()

	local TU = require 'KSTableUtils'
	
	assert( false == pcall( TU.invert, nil ), "expected invert to fail (1)" )
	assert( false == pcall( TU.invert, 'nottable' ), "expected invert to fail (2)" )
	
	assert( TU.deepEquals( TU.invert( {} ), {} ) )
	assert( TU.deepEquals( TU.invert( { a = "foo", b = "baz", c = "bar" } ),
							{ foo = "a", baz = "b", bar = "c" } ) )
										
	
	assert( false == pcall( TU.invert, { a = "foo", b = "foo" } ), "expected invert to fail, non-unique" )

end

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

function T.testFilter()
	
	local TU = require 'KSTableUtils'
	
	assert( false == pcall( TU.filter, nil, {} ), "expected filter to fail( 1 )" )
	assert( false == pcall( TU.filter, 'notfn', {} ), "expected filter to fail( 2 )" )
	assert( false == pcall( TU.filter, function() return true end, nil ), "expected filter to fail ( 3 )" )
	assert( false == pcall( TU.filter, function() return true end, 'nottable' ), "expected filter to fail ( 4 )" )
	
	assert( TU.deepEquals( TU.filter( function() return true end, {} ), {} ) )
	
	assert( TU.deepEquals( TU.filter( function() return true end, { item1 = 'here', item2 = 'there' } ), { item1 = 'here', item2 = 'there' } ) )
	
	assert( TU.deepEquals( TU.filter( function() return false end, { item1 = 'here', item2 = 'there' } ), {} ) )
	
	assert( TU.deepEquals( TU.filter( function( key, value ) return value == 'here' end, { item1 = 'here', item2 = 'there' } ), { item1 = 'here' } ) )
	
	assert( TU.deepEquals( TU.filter( function( key, value ) return key == 'item2' end, { item1 = 'here', item2 = 'there' } ), { item2 = 'there' } ) )
	
	assert( TU.deepEquals( TU.filter( function( key, value ) return value end, { item1 = 'here' } ), {} ) )
	
end

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

local function derefOptTest( name )

	return function( p )

		T[ name ] = function()

			local TU = require 'KSTableUtils'
			local src = TU.deepCopy( p.src )
			local actualResult = TU.derefOpt( src, unpack( p.path, 1, #p.path ) )
			local expectedResult = p.result

			if type( actualResult ) == 'table' and type( expectedResult ) == 'table' then
				assert( TU.deepEquals( actualResult, expectedResult ),
						"Incorrect results" )
			else
				assert( actualResult == expectedResult,
						string.format( "Incorrect result: was %s, expected %s",
									   tostring( actualResult ),
									   tostring( expectedResult ) ) )
			end

		end

	end

end

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

derefOptTest "testDerefOpt_emptyTable" {
	
	src = {},
	path = { 'a', 'b', 'c' },
	result = nil,
	
}

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

derefOptTest "testDerefOpt_threeDeep" {
	
	src = {
		a = {
			b = {
				c = 42,
			},
		},
	},
	path = { 'a', 'b', 'c' },
	result = 42,
	
}

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

derefOptTest "testDerefOpt_partialHierarchy" {
	
	src = {
		a = {},
	},
	path = { 'a', 'b', 'c' },
	result = nil,
	
}

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

derefOptTest "testDerefOpt_nonTableInMiddle" {
	
	src = {
		a = {
			b = 'foo',
		},
	},
	path = { 'a', 'b', 'c' },
	result = nil,
	
}

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

derefOptTest "testDerefOpt_notATable" {
	
	src = 56,
	path = { 'a', 'b', 'c' },
	result = nil,
	
}

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

function T.testDerefOpt_nilInPath()

	local TU = require 'KSTableUtils'

	local src = {
		a = {
			b = {
				c = 42,
			},
		},
	}

	local result = TU.derefOpt( src, 'a', nil, 'c' )

	assert( result == nil )

end

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

function T.testDerefOpt_nilPath()

	local TU = require 'KSTableUtils'

	local src = {
		a = {
			b = {
				c = 42,
			},
		},
	}

	local result = TU.derefOpt( src, nil )

	assert( result == nil )

end

function T.testApplyOpt()

	local TU = require 'KSTableUtils'

	local tbl = {
		foo = {
			bar = {
				value = 'ABCDE'
			}
		}
	}

	TU.applyOpt( tbl, string.lower, 'foo', 'bar', 'value' )

	assert( TU.deepEquals( tbl, {
		foo = {
			bar = {
				value = 'abcde'
			}
		}
	} ) )

	TU.applyOpt( tbl, 'ABCDE', 'foo', 'bar', 'value' )

	assert( TU.deepEquals( tbl, {
		foo = {
			bar = {
				value = 'ABCDE'
			}
		}
	} ) )

	tbl = {
		foo = {
			bar = {
				value = 'ABCDE'
			},
			baz = {
				value = 'FGHIJ'
			},
			other = {
				value = 'DONT STOP THE SHOUTING'
			}
		}
	}

	TU.applyOpt( tbl, string.lower, 'foo', TU.keyMatch( 'ba.*' ), 'value' )

	assert( TU.deepEquals( tbl, {
		foo = {
			bar = {
				value = 'abcde'
			},
			baz = {
				value = 'fghij'
			},
			other = {
				value = 'DONT STOP THE SHOUTING'
			}
		}
	} ) )

	tbl = {
		foo = {
			bar = {
				v1 = 'ABCDE',
				v2 = 'FGHIJ',
				value = 'STOP THE SHOUTING'
			}
		}
	}

	TU.applyOpt( tbl, string.lower, 'foo', 'bar', TU.keyMatch( 'v.*' ) )

	assert( TU.deepEquals( tbl, {
		foo = {
			bar = {
				v1 = 'abcde',
				v2 = 'fghij',
				value = 'stop the shouting'
			}
		}
	} ) )

end


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

function T.testApplyKeyOpt()

	local TU = require 'KSTableUtils'

	local tbl = {
		foo = {
			BAR = {
				value = 'abcde'
			}
		}
	}

	TU.applyKeyOpt( tbl, string.lower, 'foo', 'BAR' )

	assert( TU.deepEquals( tbl, {
		foo = {
			bar = {
				value = 'abcde'
			}
		}
	} ) )

	tbl = {
		foo = {
			BAR = {
				value = 'abcde'
			},
			BAZ = {
				value = 'fghij'
			}
		}
	}

	TU.applyKeyOpt( tbl, string.lower, 'foo', TU.keyMatch('BA.*' ) )

	assert( TU.deepEquals( tbl, {
		foo = {
			bar = {
				value = 'abcde'
			},
			baz = {
				value = 'fghij'
			}
		}
	} ) )

end

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

function T.testSetNested()

	local TU = require 'KSTableUtils'

	local tbl = { }

	TU.setNested( tbl, 17, 'foo', 'bar', 'baz' )

	assert( TU.setNested( tbl, 17 ) == false )

	assert( TU.deepEquals( tbl, {
		foo = {
			bar = {
				baz = 17,
			}
		}
	} ) )

	assert( TU.setNested( tbl, nil, 'foo', 'bar', 'baz' ) )
	
	assert( TU.deepEquals( tbl, {
		foo = {
			bar = { }
		}
	} ) )

	tbl = { foo = {} }

	TU.setNested( tbl, 17, 'foo', 'bar', 'baz' )

	assert( TU.deepEquals( tbl, {
		foo = {
			bar = {
				baz = 17,
			}
		}
	} ) )

end

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

function T.testSlice()

	local TU = require 'KSTableUtils'
	
	local tbl = { 1, 2, 3, 4 }

	assert( TU.deepEquals( TU.slice( tbl ), { 1, 2, 3, 4 } ) )

	assert( TU.deepEquals( TU.slice( tbl, 1 ), { 1, 2, 3, 4 } ) )

	assert( TU.deepEquals( TU.slice( tbl, 1, 4 ), { 1, 2, 3, 4 } ) )

	assert( TU.deepEquals( TU.slice( tbl, 2, 3 ), { 2, 3 } ) )

	-- negative indices
	assert( TU.deepEquals( TU.slice( tbl, 2, -1 ), { 2, 3 } ) )

	assert( TU.deepEquals( TU.slice( tbl, -2, -1 ), { 2, 3 } ) )

	-- steps
	assert( TU.deepEquals( TU.slice( tbl, nil, nil, 2 ), { 1, 3 } ) )

	-- negative steps

	assert( TU.deepEquals( TU.slice( tbl, 4, 1, -1 ), { 4, 3, 2, 1 } ) )

	assert( TU.deepEquals( TU.slice( tbl, 4, 1, -2 ), { 4, 2 } ) )

	assert( TU.deepEquals( TU.slice( tbl, -1, 2, -1 ), { 3, 2 } ) )
	
end

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

function T.testFlatten()

	local TU = require 'KSTableUtils'

	local tbl = { { 1 }, { 2 } , { 3 }, { 4 } }

	assert( TU.deepEquals( TU.flatten( tbl ), { 1, 2, 3, 4 } ) )

	tbl = { { 1 }, nested = { 2, 3 }, even = { deeper = { 4 } } }

	assert( TU.deepEquals( TU.flatten( tbl ), { 1, 2, 3, 4 } ) )

end

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

function T.testExtractValues()

	local TU = require 'KSTableUtils'

	local function isPermutation( a, b )

		if #a ~= #b then
			return false
		end

		for _, va in pairs( a ) do
			local found = false

			for _, vb in pairs( b ) do
				if TU.deepEquals( va, vb ) then
					found = true
					break
				end
			end

			if not found then
				return false
			end
		end

		return true
	end

	local tbl = { a = 1, b = 2, c = 3, d = 4 }

	TU.extractValues( tbl )

	assert( isPermutation( TU.extractValues( tbl ), { 1, 2, 3, 4 } ) )

	tbl = { a = { 1 } , b = { 2 }, c = { 3 }, d = { 4 } }

	assert( isPermutation( TU.extractValues( tbl ), { { 1 }, { 2 }, { 3 }, { 4 } } ) )

	tbl = { { 1 }, a = { 2, 3 }, b = { deeper = { 4 } } }

	assert( isPermutation( TU.extractValues( tbl ), { { 1 }, { 2, 3 }, { deeper = { 4 } } } ) )

	assert( TU.deepEquals( TU.extractValues( { } ), { } ) )

end

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

function T.testTrimmedMean()

	local TU = require 'KSTableUtils'

	local tbl = { 1, 2, 3, 4, 5 }

	local success = pcall( TU.trimmedMean, tbl )

	assert( not success, "Percentage required" )

	success = pcall( TU.trimmedMean, 0, tbl )

	assert( not success, "Percentage must be greater than 0" )

	success = pcall( TU.trimmedMean, 1, tbl )

	assert( not success, "Percentage must be less than 1" )

	success = pcall( TU.trimmedMean, 0.2, {} )
	
	assert( not success, "empty table, expect error" )
	
	local result = TU.trimmedMean( 0.2, tbl )
	
	assert( result == 3 )

	tbl[ #tbl + 1 ] = 100

	result = TU.trimmedMean( 0.2, tbl )
	
	assert( result == 3.5 )
	
	tbl[ #tbl + 1 ] = 0

	result = TU.trimmedMean( 0.2, tbl )
	
	assert( result == 3 )
	
end

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

--local function deepEquals_helper( a, b, depth, depthLimit )
--
--	if type( a ) == 'table' and type( b ) == 'table' then
--	
--		depth = depth + 1
--		
--		if depthLimit < depth then
--			return false
--		end
--	
--		-- All key/value pairs in a must match with b
--	
--		for k, av in pairs( a ) do
--			local bv = b[ k ]
--			if av ~= bv and not deepEquals_helper( av, bv, depth, depthLimit )
--			then
--				return false
--			end
--		end
--		
--		-- b must contain no other pairs
--		
--		for k in pairs( b ) do
--			if a[ k ] == nil then
--				return false
--			end
--		end
--		
--		return true
--	
--	end
--	
--	return false
--
--end
--
----------------------------------------------------------------------------------
--
--local function deepEquals_old( a, b, depthLimit )
--	
--	if a == b then
--		return true
--	end
--	
--	depthLimit = depthLimit or 25
--	
--	return deepEquals_helper( a, b, 1, depthLimit )
--
--end
--
----------------------------------------------------------------------------------
--
--function T.DISABLED_testDeepEqualsTestPerformance()
--
--	local function generateHugeTable_flat()
--		local t = {}
--		for i = 1, 1000 do
--			t[ tostring( i ) ] = i
--			t[ i ] = tostring( i )
--		end
--		return t
--	end
--	
--	local function generateHugeTable_layer( layer )
--		local t = {}
--		if layer == 2 then
--			for j = 1, 50 do
--				t[ tostring( j ) ] = j
--				t[ j ] = tostring( j )
--			end
--		else
--			for i = 1, 50 do
--				t[ i ] = tostring( i )
--			end
--			for i = 1, 5 do
--				t[ tostring( i ) ] = generateHugeTable_layer( layer - 1 )
--			end
--			
--		end
--		return t
--	end
--	
--	local tFlat_1 = generateHugeTable_flat()
--	local tFlat_2 = generateHugeTable_flat()
--	
--	local tLayer_1 generateHugeTable_layer( 5 )
--	local tLayer_2 generateHugeTable_layer( 5 )
--
--	local TU = require 'KSTableUtils'
--	local KSDate = require 'KSDate'
--
--	local flatEqualStart = KSDate.timeNowForAnimation()
--		for i = 1, 10000 do
--			assert( TU.deepEquals( tFlat_1, tFlat_2 ) )
--		end
--	local flatDuration = KSDate.timeNowForAnimation() - flatEqualStart
--	
--	local flatEqualOldStart = KSDate.timeNowForAnimation()
--		for i = 1, 10000 do
--			assert( deepEquals_old( tFlat_1, tFlat_2 ) )
--		end
--	local flatDurationOld = KSDate.timeNowForAnimation() - flatEqualOldStart
--	
--	assert( flatDuration == flatDurationOld, tostring( flatDuration/flatDurationOld ) )
--	
--	local layerEqualStart = KSDate.timeNowForAnimation()
--		for i = 1, 1000 do
--			assert( TU.deepEquals( tLayer_1, tLayer_2 ) )
--		end
--	local layerDuration = KSDate.timeNowForAnimation() - layerEqualStart
--	
--	local layerEqualOldStart = KSDate.timeNowForAnimation()
--		for i = 1, 1000 do
--			assert( deepEquals_old( tLayer_1, tLayer_2 ) )
--		end
--	local layerDurationOld = KSDate.timeNowForAnimation() - layerEqualOldStart
--	
--	assert( layerDuration == layerDurationOld, tostring( layerDuration/layerDurationOld ) )
--
--end

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

return T
