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

KSPromisesTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2011 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 Rx = require 'Rx'

local KSPromisesTest = {}

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

local assert = assert

local KSTestUtils = require 'KSTestUtils'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- wichitafoundation.core.self_test
local WFAsyncTestCase = require 'WFAsyncTestCase'

	-- wichitafoundation.core.threading
local WFWorkerThread = require 'WFWorkerThread'

local TEST_TIMEOUT = WFPlatformInfo.isAndroid and 3 or 2

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

function KSPromisesTest.setUp()

	local KSPromises = require 'KSPromises'
	
	if not KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ) then
		debug.print( "Run until done set should start out empty" )
	end
	
	KSPromises.testing_resetRunUntilDonePool()
	
end

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

function KSPromisesTest.tearDown()

	local KSPromises = require 'KSPromises'
	local WFCloseSupport = require 'WFCloseSupport'
	
	if not KSPromises.testing_runUntilDonePoolIsEmpty() then
		collectgarbage()
		collectgarbage()
		collectgarbage()
		WFCloseSupport.processGCQueue()
	end
	
	local tback
	
	local function debugEntries( msg, entry, entryTback )
		tback = entryTback
		debug.print( msg, entry, entryTback )
	end
	
	if not KSPromises.testing_runUntilDonePoolIsEmpty( debugEntries ) then
		assert( false, "Run until done should finish empty: " .. tostring( tback ) )
	end
	
	KSPromises.testing_resetRunUntilDonePool()
	
end

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

function KSPromisesTest.testPromiseP()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local resolvedValue = nil
	
	promise:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
		local _, value = ...
		resolvedValue = value
	end ):runUntilDone()
	
	assert( resolvedValue == nil, "Resolved value should still be nil" )
	
	resolver( 'data', 5 )
	
	assert( resolvedValue == 5, "Resolved value should now be 5." )
	
	KSTestUtils.assertFails( resolver, 'data', 4 ) -- Disallow multiple resolution

end

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

function KSPromisesTest.testPromiseStopResolution()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP( true )
	
	local wasResolved = false
	
	promise:whenResolvedDo( function( ... )
		assert( select( '#', ... ) == 0, "should be resolved with no parameters" )
		assert( not wasResolved, "should only resolve once" )
		wasResolved = true
	end ):runUntilDone()
	
	resolver:stopResolution()

	assert( wasResolved, "should have been resolved with no parameters" )
	
	resolver( 'data', 5 ) -- verify we don't try to resolve after stopped

end

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

function KSPromisesTest.testPromiseSequence()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local t = { }
	
	local idx = 0
	
	local function makeAction()
		idx = idx + 1
		local my_idx = idx
		return function( ... ) t[ #t + 1 ] = my_idx; return ... end
	end
	
	promise:mapDataP( makeAction() ):mapDataP( makeAction() ):runUntilDone()
	promise:mapDataP( makeAction() ):runUntilDone()
	local q = promise:mapDataP( makeAction() )
	q:mapDataP( makeAction() ):runUntilDone()
	q:mapDataP( makeAction() ):mapDataP( makeAction() ):runUntilDone()
	
	resolver( 'data', 5 )
	
	assert( idx == 7, "Should have seven actions" )
	
	for i = 1, 7 do
		assert( t[ i ] == i, string.format( "Wrong action at step %d", i ) )
	end

end

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

function KSPromisesTest.testMapPromise()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2 = promise:mapP( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
		local _, value = ...
		return 'data', value + 1
	end )
	
	local resolvedValue = nil
	
	promise2:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 6 )( ... )
		local _, value = ...
		resolvedValue = value
	end ):runUntilDone()
	
	assert( resolvedValue == nil, "Resolved value should still be nil" )
	
	resolver( 'data', 5 )
	
	assert( resolvedValue == 6, "Resolved value should now be 5." )

end

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

function KSPromisesTest.testMapPromiseViaTable()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2 = promise:mapP{ data = function( ... )
		KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
		local _, value = ...
		return 'data', value + 1
	end }
	
	local resolvedValue = nil
	
	promise2:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 6 )( ... )
		local _, value = ...
		resolvedValue = value
	end ):runUntilDone()
	
	assert( resolvedValue == nil, "Resolved value should still be nil" )
	
	resolver( 'data', 5 )
	
	assert( resolvedValue == 6, "Resolved value should now be 5." )

end

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

function KSPromisesTest.testMapDataPromiseWithData()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2 = promise:mapDataP( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
		local _, value = ...
		return 'data', value + 1
	end )
	
	local resolvedValue = nil
	
	promise2:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 6 )( ... )
		local _, value = ...
		resolvedValue = value
	end ):runUntilDone()
	
	assert( resolvedValue == nil, "Resolved value should still be nil" )
	
	resolver( 'data', 5 )
	
	assert( resolvedValue == 6, "Resolved value should now be 5." )

end

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

function KSPromisesTest.testMapDataPromiseWithNonData()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2 = promise:mapDataP( function( ... )
		assert( false, "We should not have gotten here" )
	end )
	
	local resolvedStatus, resolvedValue
	
	promise2:whenResolvedDo( function( ... )
		resolvedStatus, resolvedValue = ...
	end ):runUntilDone()
	
	resolver( 'flerb', 5 )
		-- This should bypass the mapping
		
	assert( resolvedStatus == 'flerb' and resolvedValue == 5,
			"Unexpected resolved results." )

end

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

function KSPromisesTest.testResolveViaPromise()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2, resolver2
	
	local promise3 = promise:mapP{ data = function( ... )
		KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
		promise2, resolver2 = KSPromises.promiseP()
		return promise2
	end }
	
	local resolvedValue = nil
	
	promise3:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 7 )( ... )
		local _, value = ...
		resolvedValue = value
	end ):runUntilDone()
	
	assert( resolvedValue == nil, "Resolved value should still be nil" )
	
	resolver( 'data', 5 )
	
	assert( resolvedValue == nil, "Resolved value should still be nil (2)" )
	
	resolver2( 'data', 7 )
	
	assert( resolvedValue == 7, "Resolved value should now be 7." )

end

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

function KSPromisesTest.testResolveViaPromiseWithExtraReturnValue()

	local KSPromises = require 'KSPromises'

	-- this test is done only to increase code coverage, you shouldn't return anything after a promise
	-- hit a debug.print line when returning extra junk after a promise
	local promise = KSPromises.resolvedPromiseP( 'data', 42 ):mapP( function() return KSPromises.canceledPromiseP(), 'ignoredReturnValue' end ):runUntilDoneP()

	assert( promise:isCanceled(), "Promise should be canceled" )

end

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

function KSPromisesTest.testResolvedPromises()

	local KSPromises = require 'KSPromises'
	
	local p = KSPromises.resolvedPromiseP( 'data', 3 )
	
	local checked = false
	
	p:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 3 )( ... )
		checked = true
	end ):runUntilDone()
	
	assert( checked, "We did not execute the action" )
	
	KSTestUtils.assertFails( KSPromises.resolvedPromiseP )
	KSTestUtils.assertFails( KSPromises.resolvedPromiseP, 3 )
	
end

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

function KSPromisesTest.testAttachToSource()

	local KSPromises = require 'KSPromises'
	
	local MinimalOwner = Rx.Class 'MinimalOwner' {
	
		require 'WFCloseSupport'.addCloseSupportMethods
	
	}
	
	local weak = setmetatable( { }, { __mode = 'kv' } )
	
	local p1, r1 = KSPromises.promiseP()
	
	local p2 = p1:mapP{ } -- The null map!
	
	rawset( p2, 'ownership', MinimalOwner() )
	
	weak.p2 = p2
	
	assert( p2, "We should have a promise" )
	
	weak.a2 = p2:whenResolvedDo( function() end ):attachTo( rawget( p2, 'ownership' ) )
	
	assert( weak.a2, "We should have an action" )
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( weak.a2, "It should survive GC if the promise is still alive" )
	
	p2 = nil
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( not weak.a2, "The action should have been collected" )
	assert( not weak.p2, "The promise should have been collected" )
	
	assert( p1, "We should still have the base promise" )
	assert( r1, "We should still have the base resolver" )
	
	r1:close()

end

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

function KSPromisesTest.testAttachToSource2()

	local KSPromises = require 'KSPromises'
	
	local MinimalOwner = Rx.Class 'MinimalOwner' {
	
		require 'WFCloseSupport'.addCloseSupportMethods
	
	}
	
	local weak = setmetatable( { }, { __mode = 'kv' } )
	
	local p1, r1 = KSPromises.promiseP()
	
	local p2 = p1:mapP{ } -- The null map!
	
	weak.p2 = p2
	
	assert( p2, "We should have a promise" )
	
	local x = nil
	
	rawset( p2, 'ownership', MinimalOwner() )
	
	weak.a2 = p2:whenResolvedDo( function( status, value ) x = value end )
						:attachTo( rawget( p2, 'ownership' ) )
	
	assert( weak.a2, "We should have an action" )
	
	weak.a3 = p2:whenResolvedDo( function() end ):runUntilDone()
		-- Attach another action into the run until done set
		
	p2 = nil
	
	assert( not KSPromises.testing_runUntilDonePoolIsEmpty(),
			"Run until done set should not be empty" )
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	r1( 'data', 7 )
	
	assert( KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ),
		"Run until done set should be empty" )
	
	assert( x == 7, "We should still have set x to 7" )
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( not weak.a3, "The run until done promise should be gone" )
	assert( not weak.p2, "The second stage promise should be gone" )
	assert( not weak.a2, "The source attached promise should be gone" )

end

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

function KSPromisesTest.testActionClosure()

	local KSPromises = require 'KSPromises'
	
	local p, r = KSPromises.promiseP()
	
	local a1 = p:whenResolvedDo( function() end ):runUntilDone()
	
	local x = nil
	
	local a2 = p:whenResolvedDo{
		data = function( _, value ) x = value end
	}:runUntilDone()
	
	assert( a2 )
	
	a1:close()
	
	r( 'data', "Hello!" )
	
	assert( x == "Hello!", "Closing one action should not terminate the promise" )
	
end

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

function KSPromisesTest.testActionThrowingError()

	local KSPromises = require 'KSPromises'
	
	local p, r = KSPromises.promiseP()
	
	local resolvedValue
	
	p:whenResolvedDo( function( status, value)
			resolvedValue = value
			error "Throw error here to validate execution continues" -- increases coverage by calling handleActionError
		end ):runUntilDone()
	
	r( 'data', 42 )
	
	assert( resolvedValue == 42 )
end

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

function KSPromisesTest.testCloseResolver()

	local KSPromises = require 'KSPromises'
	
	local holder = { }
	
	local p, r = KSPromises.promiseP()
	
	p:whenResolvedDo( function() end ):attachTo( nil, holder, 'foo' )
	
	assert( holder.foo, "We should be holding the action" )
	
	r:close()
	
	assert( holder.foo == nil, "We should have collected the action" )

end

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

function KSPromisesTest.testGCResolver()

	local KSPromises = require 'KSPromises'
	local WFCloseSupport = require 'WFCloseSupport'
	
	local holder = { }
	
	local promise, resolver = KSPromises.promiseP()
	
	assert( resolver, "We should have a resolver" )
	
	promise:whenResolvedDo( function() end ):attachTo( nil, holder, 'foo' )
	
	assert( holder.foo, "We should be holding the action" )
	
	resolver = nil
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( WFCloseSupport.processGCQueue(), "Expect object in GC Queue." )
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( holder.foo == nil, "We should have collected the action" )

end

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

function KSPromisesTest.testResolveWithNoParams()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local wasResolved = false
	
	promise:whenResolvedDo( function( ... )
		assert( select( '#', ... ) == 0, "promise should resolve as canceled" )
		wasResolved = true
	end ):runUntilDone()
	
	resolver() -- this should be treated the same as resolver( 'canceled' )
	
	assert( wasResolved, "promise should be resolved" )
	
end

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

function KSPromisesTest.testAddPromiseResolverMethods()

	local KSPromises = require 'KSPromises'
	local WFCloseSupport = require 'WFCloseSupport'
	local WFArguments = require 'WFArguments'
	
	local resolverWasClosed = false
	
	local customPromiseResolverMethods = {
	
		_didResolve = false,
	
		_obeysStopResolution = false,

		__init = function( self, obeysStopResolution )
			self._obeysStopResolution = obeysStopResolution or false
			resolverWasClosed = false
		end,
		
		__call = function( self, ... )
	
			WFArguments.check( 'CustomPromiseResolver' )
			
			assert( not self._didResolve, "Promise has already been resolved." )
			
			self._didResolve = true
			
			if self:canResolvePromise() then
				self:resolvePromise( ... )
			end
	
			self:close()
	
		end,

		close = function ( self)
			resolverWasClosed = true
		end,

--		startResolution = nop, 	-- increase code coverage - don't provide a startResolution method

		stopResolution = function( self )
			-- increase code coverage by generating an error in the stopResolution method, this will also exercise the local doClose method in KSPromises
			if self._obeysStopResolution then
				error "oops! verify we still closed the promise"
			end
		end,

		KSPromises.addPromiseResolverMethods,
		
		WFCloseSupport.addCloseSupportMethods,
	}


	local customPromiseResolver_class = Rx.Class 'CustomPromiseResolver'( customPromiseResolverMethods )
	
	do
		local resolver = customPromiseResolver_class()
		
		local promise = KSPromises.promiseForResolverP( resolver, 'retained | closeTarget' )
		
		local wasResolved = false
		
		promise:whenResolvedDo( function( ... )
			KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
			wasResolved = true
		end ):runUntilDone()
		
		assert( resolver:canResolvePromise(), "verify the canResolve method was added and works" )
	
		resolver( 'data', 5 )
		
		assert( wasResolved, "promise should be resolved" )
		
		assert( resolverWasClosed, "resolver should be closed" )
		
		assert( not resolver:canResolvePromise(), "promise is already resolved, can't resolve again" )
	end
	
	
	do
		local resolver = customPromiseResolver_class( true )

		local promise = KSPromises.promiseForResolverP( resolver, 'retained | closeTarget' )
		
		local wasResolved = false
		
		local promiseAction = promise:whenResolvedDo( function( ... )
			KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
			wasResolved = true
		end )
		
		promiseAction:runUntilDone()
		
		promiseAction:close() -- This will cause the promiseSource to close the resolver
		
		assert( not resolver:canResolvePromise(), "no action, so can't resolve" )

		resolver( 'data', 5 )

		assert( not wasResolved, "promise should not have been resolved" )
		
		assert( resolverWasClosed, "resolver should be closed" )
	end
	
end

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

function KSPromisesTest.testPromisePreservation()

	local KSPromises = require 'KSPromises'
	
	local holder = { }
	local weak = setmetatable( { }, { __mode = 'kv' } )
	
	local p, r = KSPromises.promiseP()
	
	weak.promise = p
	weak.resolver = r
	
	weak.action = p:whenResolvedDo( function() end ):attachTo( nil, holder, 'foo' )
	
	assert( holder.foo, "We should be holding the action" )
	
	p = nil
	
	collectgarbage()
	collectgarbage()
	collectgarbage()

	assert( weak.promise, "Promise should still be alive" )
	assert( weak.action, "Action should still be alive" )
	assert( weak.resolver, "Resolver should still be alive" )

	r( 'data', 3 )
	
	r = nil
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( not weak.promise, "Promise should not be alive" )
	assert( not weak.action, "Action should not be alive" )
	assert( not weak.resolver, "Resolver should not be alive" )

end

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

local function checkTestResultsAndNotify( test, ... )
	local n = select( '#', ... )
	local expectedResults = { ... }
	return function( ... )
		if pcall( function( ... )
				KSTestUtils.assertMatchArgs( unpack( expectedResults, 1, n ) )( ... )
			end, ... ) == true
		then
			test:notifyTestDidSucceed()
		else
			test:notifyTestDidFail()
		end
	end
end

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

function KSPromisesTest.testAsyncResolution( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local p, r = KSPromises.promiseP()
	
	p:whenResolvedDo( checkTestResultsAndNotify( test, 'data', "Hello!" ) ):runUntilDone()
	
	WFIdle.onIdleDo( function() r( 'data', "Hello!" ) end )
	
	test:waitForTestToFinish( TEST_TIMEOUT * 250 )

end

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

-- Test a bug report from Eric Wright...

function KSPromisesTest.testImmediateResolution()

	local KSPromises = require 'KSPromises'
	
	local function makeResolvedP()
		local p, r = KSPromises.promiseP()
		r( 'error', 'not implemented' )
		return p
	end
		
	local pp = makeResolvedP()
	
	local didIt = false
	
	pp:whenResolvedDo( function() didIt = true end ):runUntilDone()
	
	assert( didIt, "We should have executed the action immediately." )

end

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

function KSPromisesTest.testAllPWithNil( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local tested = 0
	local expectedTests = 0

	local function isNumberP( x )
	
		return WFIdle.onIdleP():mapP( function()
			tested = tested + 1
			if type( x ) == 'number' then
				return 'data', x
			else
				return 'error', 'not a number'
			end
		end )
		
	end
	
	local function checkPromiseForExpectedResultP( p, expectedStatus, expectedValue )
	
		return p:mapP( function( status, value )
			if status == expectedStatus and value == expectedValue then
				return 'data', true
			else
				return 'error', 'unexpected result'
			end
		end )
	
	end
	
	local function testArrayP( expectedSuccess, array  )
	
		if expectedSuccess then
			local i = 0
			while array[ i + 1 ] ~= nil do
				i = i + 1
			end
			expectedTests = expectedTests + i
		end
	
		local p = KSPromises.allP( isNumberP, array )
		
		return checkPromiseForExpectedResultP( p,
							expectedSuccess and 'data' or 'error',
							expectedSuccess and true or 'not a number' )
	
	end
	
	local function testP( entry )
		return testArrayP( entry[ 1 ], entry[ 2 ] )
	end
	
	local p = KSPromises.allP( testP, {
		{ false, { 1, 2, nil, 4, n = 4 } },	-- nil should not break the array
		{ false, { 1, 2, nil, nil, nil, nil, nil, 8, n = 8 } },	-- nil should not break the array
		{ true, { 1, 2, 3, 'foo', n = 3 } },	-- n breaks the array
	} )
	
	-- Note that we only require that we've tested all of the subcases for the
	-- success cases since we don't know what order the cases will be run in.
	
	p:whenResolvedDo( function( status, value )
			if status == 'data' and value == true and expectedTests <= tested then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )

end

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

function KSPromisesTest.testAllP( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local tested = 0
	local expectedTests = 0

	local function isNumberP( x )
	
		return WFIdle.onIdleP():mapP( function()
			tested = tested + 1
			if type( x ) == 'number' then
				return 'data', x
			else
				return 'error', 'not a number'
			end
		end )
		
	end
	
	local function checkPromiseForExpectedResultP( p, expectedStatus, expectedValue )
	
		return p:mapP( function( status, value )
			if status == expectedStatus and value == expectedValue then
				return 'data', true
			else
				return 'error', 'unexpected result'
			end
		end )
	
	end
	
	local function testArrayP( expectedSuccess, array  )
	
		if expectedSuccess then
			local i = 0
			while array[ i + 1 ] ~= nil do
				i = i + 1
			end
			expectedTests = expectedTests + i
		end
	
		local p = KSPromises.allP( isNumberP, array )
		
		return checkPromiseForExpectedResultP( p,
							expectedSuccess and 'data' or 'error',
							expectedSuccess and true or 'not a number' )
	
	end
	
	local function testP( entry )
		return testArrayP( entry[ 1 ], entry[ 2 ] )
	end
	
	local p = KSPromises.allP( testP, {
		{ true, { } },			-- empty array
		{ true, { 1 } },		-- single value
		{ true, { 1, 2 } },		-- two values
		{ true, { 1, 2, 3 } },	-- three values
		{ true, { 1, 2, nil, 'foo' } },	-- nil breaks the array
		{ false, { 1, 2, 'foo' } }	-- check that we can fail at all
	} )
	
	-- Note that we only require that we've tested all of the subcases for the
	-- success cases since we don't know what order the cases will be run in.
	
	p:whenResolvedDo( function( status, value )
			if status == 'data' and value == true and expectedTests <= tested then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )

end

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

function KSPromisesTest.testParallelAllP( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local tested = 0
	local expectedTests = 0

	local function isNumberP( x )
	
		return WFIdle.onIdleP():mapDataP( function()
			tested = tested + 1
			if type( x ) == 'number' then
				return 'data', x
			else
				return 'error', 'not a number'
			end
		end )
		
	end
	
	local function checkPromiseForExpectedResultP( p, expectedStatus, expectedValue )
	
		return p:mapP( function( status, value )
			if status == expectedStatus and value == expectedValue then
				return 'data', true
			else
				return 'error', 'unexpected result'
			end
		end )
	
	end
	
	local function testArrayP( expectedSuccess, array  )
	
		if expectedSuccess then
			local i = 0
			while array[ i + 1 ] ~= nil do
				i = i + 1
			end
			expectedTests = expectedTests + i
		end
	
		local p = KSPromises.allP( isNumberP, array, 2 )
		
		return checkPromiseForExpectedResultP( p,
							expectedSuccess and 'data' or 'error',
							expectedSuccess and true or 'not a number' )
	
	end
	
	local function testP( entry )
		return testArrayP( entry[ 1 ], entry[ 2 ] )
	end
	
	local p = KSPromises.allP( testP, {
		{ true, { } },			-- empty array
		{ true, { 1 } },		-- single value
		{ true, { 1, 2 } },		-- two values
		{ true, { 1, 2, 3 } },	-- three values
		{ true, { 1, 2, nil, 'foo' } },	-- nil breaks the array
		{ false, { 1, 2, 'foo' } }	-- check that we can fail at all
	}, 4 )
	
	-- Note that we only require that we've tested all of the subcases for the
	-- success cases since we don't know what order the cases will be run in.
	
	p:whenResolvedDo( function( status, value )
			if status == 'data' and value == true and expectedTests <= tested then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )

end

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

function KSPromisesTest.testMapAllP( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local tested = 0
	local expectedTests = 0

	local function isNumberP( x )
	
		return WFIdle.onIdleP():mapDataP( function()
			tested = tested + 1
			if type( x ) == 'number' then
				return 'data', x
			else
				return 'error', 'not a number'
			end
		end )
		
	end
	
	local function makeCheckForExpectedResult( expectedStatus, expectedValue )
	
		return function( status, value )
			if status == expectedStatus and value == expectedValue then
				return 'data', true
			else
				return 'error', 'unexpected result'
			end
		end
	
	end
	
	local function testArrayP( expectedSuccess, array  )
	
		if expectedSuccess then
			local i = 0
			while array[ i + 1 ] ~= nil do
				i = i + 1
			end
			expectedTests = expectedTests + i
		end
	
		local p = WFIdle.onIdleP():mapDataP( function()
			return 'data', array
		end )
		
		local pp = p:mapAllP( isNumberP )
		
		local ppp = pp:mapP( makeCheckForExpectedResult(
									expectedSuccess and 'data' or 'error',
									expectedSuccess and true or 'not a number' ) )
		
		return ppp
	
	end
	
	local function testP( entry )
		return testArrayP( entry[ 1 ], entry[ 2 ] )
	end
	
	local p = KSPromises.allP( testP, {
		{ true, { } },			-- empty array
		{ true, { 1 } },		-- single value
		{ true, { 1, 2 } },		-- two values
		{ true, { 1, 2, 3 } },	-- three values
		{ true, { 1, 2, nil, 'foo' } },	-- nil breaks the array
		{ false, { 1, 2, 'foo' } }	-- check that we can fail at all
	} )
	
	-- Note that we only require that we've tested all of the subcases for the
	-- success cases since we don't know what order the cases will be run in.
	
	p:whenResolvedDo( function( status, value )
			if status == 'data' and value == true and expectedTests <= tested then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )

end

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

function KSPromisesTest.testParallelMapAllP( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local tested = 0
	local expectedTests = 0

	local function isNumberP( x )
	
		return WFIdle.onIdleP():mapDataP( function()
			tested = tested + 1
			if type( x ) == 'number' then
				return 'data', x
			else
				return 'error', 'not a number'
			end
		end )
		
	end
	
	local function makeCheckForExpectedResult( expectedStatus, expectedValue )
	
		return function( status, value )
			if status == expectedStatus and value == expectedValue then
				return 'data', true
			else
				return 'error', 'unexpected result'
			end
		end
	
	end
	
	local function testArrayP( expectedSuccess, array  )
	
		if expectedSuccess then
			local i = 0
			while array[ i + 1 ] ~= nil do
				i = i + 1
			end
			expectedTests = expectedTests + i
		end
	
		local p = WFIdle.onIdleP():mapDataP( function() return 'data', array end )
		
		local pp = p:mapAllP( isNumberP, 2 )
		
		local ppp = pp:mapP( makeCheckForExpectedResult(
									expectedSuccess and 'data' or 'error',
									expectedSuccess and true or 'not a number' ) )
		
		return ppp
	
	end
	
	local function testP( entry )
		return testArrayP( entry[ 1 ], entry[ 2 ] )
	end
	
	local p = KSPromises.allP( testP, {
		{ true, { } },			-- empty array
		{ true, { 1 } },		-- single value
		{ true, { 1, 2 } },		-- two values
		{ true, { 1, 2, 3 } },	-- three values
		{ true, { 1, 2, nil, 'foo' } },	-- nil breaks the array
		{ false, { 1, 2, 'foo' } }	-- check that we can fail at all
	}, 4 )
	
	-- Note that we only require that we've tested all of the subcases for the
	-- success cases since we don't know what order the cases will be run in.
	
	p:whenResolvedDo( function( status, value )
			if status == 'data' and value == true and expectedTests <= tested then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )

end

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

function KSPromisesTest.testHugeAllP_testNightly( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local count = 20000

	local tested = 0
	local expectedTests = count
	
	local array = { }
	
	for i = 1, count do
		array[ i ] = i
	end
	
	local function isNumberP( x )
	
		local isNumber = type( x ) == 'number'
	
		tested = tested + 1
		
		-- Test even numbers immediately

		if x % 2 == 0 then
			return KSPromises.resolvedPromiseP( 'data', x )
		end
		
		-- Test odd numbers at idle time
		
		return WFIdle.onIdleP():mapDataP( function()
			if isNumber then
				return 'data', x
			else
				return 'error', 'not a number'
			end
		end )
			
	end
	
	local p = KSPromises.allP( isNumberP, array )
	
	p:whenResolvedDo( function( status, value )
			if status == 'data' and value == true and expectedTests <= tested then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT * 30 )

end

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

function KSPromisesTest.testParallelHugeAllP_testNightly( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local count = 20000

	local tested = 0
	local expectedTests = count
	
	local array = { }
	
	for i = 1, count do
		array[ i ] = i
	end
	
	local function isNumberP( x )
	
		local isNumber = type( x ) == 'number'
	
		tested = tested + 1
		
		-- Test even numbers immediately

		if x % 2 == 0 then
			return KSPromises.resolvedPromiseP( 'data', x )
		end
		
		-- Test odd numbers at idle time
		
		return WFIdle.onIdleP():mapDataP( function()
			if isNumber then
				return 'data', x
			else
				return 'error', 'not a number'
			end
		end )
	
	end
	
	local p = KSPromises.allP( isNumberP, array, 10 )
	
	p:whenResolvedDo( function( status, value )
			if status == 'data' and value == true and expectedTests <= tested then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT * 30 )

end

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

function KSPromisesTest.testVeryParallelHugeAllP_testNightly( test )
--function KSPromisesTest.testVeryParallelHugeAllP( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local count = 20000

	local tested = 0
	local expectedTests = count
	
	local array = { }
	
	for i = 1, count do
		array[ i ] = i
	end
	
	local function isNumberP( x )
	
		local isNumber = type( x ) == 'number'
	
		tested = tested + 1
		
		-- Test even numbers immediately

		if x % 2 == 0 then
			return KSPromises.resolvedPromiseP( 'data', x )
		end
		
		-- Test odd numbers at idle time
		
		return WFIdle.onIdleP():mapDataP( function()
			if isNumber then
				return 'data', x
			else
				return 'error', 'not a number'
			end
		end )
			
	end
	
	local p = KSPromises.allP( isNumberP, array, count * 2 )
	
	p:whenResolvedDo( function( status, value )
			if status == 'data' and value == true and expectedTests <= tested then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT * 30 )

end

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

local function delayResultP( p )

	return p:mapP( function( ... )
		local values = { ... }
		local n = select( '#', ... )
		return require 'WFIdle'.laterP():mapP( function()
			return unpack( values, 1, n )
		end )
	end )

end

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

-- Test that we work with promises that have been resolved before they pass to
-- allP.

function KSPromisesTest.testResolvedAllP( test )
	
	local KSPromises = require 'KSPromises'

	local allP = KSPromises.allP( function( value )
		return KSPromises.allP( function( value )
			return KSPromises.resolvedPromiseP( "data", "resolved", value )
		end, { 1 } )
		:mapP( function()
		end )
	end, { 1 } )
	
	delayResultP( allP ):whenResolvedDo( function( status, value )
		assert( status == "data" )
		assert( value == true )
		test:notifyTestDidSucceed()
	end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )

end

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

function KSPromisesTest.testErrorCancelsAllP( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local resolvers = { }
	
	local function testP( value )
	
		if value == 3 then
			return WFIdle.onIdleP():mapP( function()
				return 'error', "Failure!"
			end )
		end
	
		local p, r = KSPromises.promiseP()
		
		resolvers[ #resolvers + 1 ] = r
		
		return p
	
	end

	local allP = KSPromises.allP( testP, { 1, 2, 3, 4 }, 10 )

	delayResultP( allP ):whenResolvedDo( function( status, value )
		assert( status == "error" )
		test:notifyTestDidSucceed()
	end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )
	
	-- FIXME: Check that the resolvers all get canceled

end

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

function KSPromisesTest.testResolvedRunUntilDone()

	local KSPromises = require 'KSPromises'
	
	local allP = KSPromises.allP( function( value )
		return KSPromises.allP( function( value )
			return KSPromises.resolvedPromiseP( "data", "resolved", value )
		end, { 1 } )
		:mapP( function()
		end )
	end, { 1 } )
	
	assert( KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ),
		"Run until done pool should be empty before running" )

	local success = false
	
	allP:whenResolvedDo( function( status, value )
		assert( status == "data" )
		assert( value == true )
		success = true
	end ):runUntilDone()

	assert( success, "We should have run already" )
	
	assert( KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ),
		"Run until done pool should be empty after running" )

end

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

function KSPromisesTest.testRunUntilDoneDebugEntries()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	assert( KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ),
		"Run until done pool should be empty before running" )

	local success = false
	
	promise:whenResolvedDo( function( status, value )
		assert( status == "data" )
		assert( value == true )
		success = true
	end ):runUntilDone()

	assert( not KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ),
		"Run until done pool should have something in it, print it out" )

	resolver( 'data', true )
	
	assert( success, "We should have run already" )
	
	assert( KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ),
		"Run until done pool should be empty after running" )

end

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

function KSPromisesTest.testResetRunUntilDone()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	assert( KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ),
		"Run until done pool should be empty before running" )

	local success = false
	
	promise:whenResolvedDo( function( status, value )
		assert( status == "data" )
		assert( value == true )
		success = true
	end ):runUntilDone()

	assert( not KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ),
		"Run until done pool should have something in it, print it out" )
		
	KSPromises.testing_resetRunUntilDonePool()
	
	assert( KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ),
		"Run until done pool should be empty after reset" )

	resolver( 'data', true )
	
	assert( success, "We should have run already" )
	
	assert( KSPromises.testing_runUntilDonePoolIsEmpty( debug.print ),
		"Run until done pool should still be empty" )

end

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

function KSPromisesTest.testRunUntilDoneP()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local resolvedValue = nil

	local localRunUntilDonePool = KSPromises.makeRunUntilDonePool() -- make our own runUntilDonePool so we can check the count, also increases code coverage
	
	assert( localRunUntilDonePool:count() == 0, "should have 0 nodes in the runUntilDone pool" )

	promise:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
		local _, value = ...
		resolvedValue = value
	end ):runUntilDone(localRunUntilDonePool)

	assert( localRunUntilDonePool:count() == 1, "should have 1 node in the runUntilDone pool" )

	local mapWasResolved = false

	local mapP = promise:mapDataP( function( status, value)
			mapWasResolved = status == 'data' and value == 5
			if mapWasResolved then
				return 'data', true
			else
				return 'error', "unexpected values passed to mapP"
			end
		end )
		:runUntilDoneP(localRunUntilDonePool)

	assert( localRunUntilDonePool:count() == 2, "should have 2 nodes in the runUntilDone pool" )

	assert( KSPromises.isPromise( mapP ), "runUntilDoneP should return a promise" )
	
	assert( resolvedValue == nil, "Resolved value should still be nil" )
	assert( not mapWasResolved, "mapWasResolved should still be false" )
	
	resolver( 'data', 5 )
	
	assert( localRunUntilDonePool:count() == 0, "should have 0 nodes in the runUntilDone pool after we resolve" )

	assert( resolvedValue == 5, "Resolved value should now be 5." )

	assert( mapWasResolved, "failed to call mapDataP's function with 'data', 5" )
	
end

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

function KSPromisesTest.testRunAttachedTo()

	local KSPromises = require 'KSPromises'
	
	local holder = { }
	
	local promise, resolver = KSPromises.promiseP()
	
	assert( resolver, "We should have a resolver" )

	local resolved = false

	promise:observeP( function() resolved = true end )
		:runAttachedTo( nil, holder, 'foo' )
	
	assert( holder.foo, "We should be holding the action" )
	
	resolver( 'data', 42 )

	assert( resolved, "We should have observed resolution" )
end

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

function KSPromisesTest.testCanceledPromises( test )

	local KSPromises = require 'KSPromises'
	
	do

		local promise = KSPromises.canceledPromiseP()
		
		assert( KSPromises.isPromise( promise ) == true, "A cancelled promise should have been constructed" )
		assert( promise:isCanceled(), "Promise should be canceled" )
		
		promise:runUntilDone() -- Just to get it claimed and out of limbo
		
	end
	
	do

		local promise = KSPromises.resolvedPromiseP( 'canceled' ) -- increase coverage in encodePromiseValue
		
		assert( KSPromises.isPromise( promise ), "A cancelled promise should have been constructed" )
		assert( promise:isCanceled(), "Promise should be canceled" )
		
		promise:runUntilDone()

	end

	do

		local promise = KSPromises.resolvedPromiseP( 'data', 42 )
			:mapP( function()
				 return 'canceled' -- increase coverage in KSPromise_finishPromiseTransform
			end ):runUntilDoneP()

		assert( promise:isCanceled(), "Promise should be canceled" )

	end

end

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

function KSPromisesTest.testUsingDo( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local obj = {
		_state = 'open',
		close = function( self ) self._state = 'closed' end
	}
	
	local resourceP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', obj ) )
		
	local function processFnP( rsrc )
		return WFIdle.laterP():mapDataP( function() return 'data', rsrc._state end )
	end
		
	local resultP = resourceP:usingDoP( processFnP )
		
	resultP = resultP:mapP( function( status, value )
		assert( status == 'data', "Promise chain did not resolve successfully" )
		assert( value == 'open', "We should have captured the open state" )
		assert( obj._state == 'closed', "The object should now be closed" )
		return 'data', true
	end )
	
	WFAsyncTestCase( test ):waitFor( resultP ):continueWhenValueMatches( 'data', true )

end

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

function KSPromisesTest.testUsingDoSlowRelease( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local obj = {
		_state = 'open',
		close = function( self ) self._state = 'closed' end
	}
	
	local resourceP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', obj ) )
		
	local function releaseFnP( rsrc )
		rsrc:close()
		return WFIdle.laterP()
	end
		
	local function processFnP( rsrc )
		return WFIdle.laterP():mapDataP( function() return 'data', rsrc._state end )
	end
		
	local resultP = resourceP:usingDoP( releaseFnP, processFnP )
		
	resultP = resultP:mapP( function( status, value )
		assert( status == 'data', "Promise chain did not resolve successfully" )
		assert( value == 'open', "We should have captured the open state" )
		assert( obj._state == 'closed', "The object should now be closed" )
		return 'data', true
	end )
	
	WFAsyncTestCase( test )
		:waitFor( resultP )
		:continueWhenValueMatches( 'data', true )

end

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

function KSPromisesTest.testUsingDoNoReleaseFn( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local obj = {
		_state = 'open',
		close = function( self ) self._state = 'closed' end
	}
	
	local resourceP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', obj ) )
		
	local function processFnP( rsrc )
		return WFIdle.laterP():mapDataP( function() return 'data', rsrc._state end )
	end
		
	local resultP = resourceP:usingDoP( nil, processFnP ) -- don't call close, just process
		
	resultP = resultP:mapP( function( status, value )
		assert( status == 'data', "Promise chain did not resolve successfully" )
		assert( value == 'open', "We should have captured the open state" )
		assert( obj._state == 'open', "The object should still be open" )
		return 'data', true
	end )
	
	WFAsyncTestCase( test ):waitFor( resultP ):continueWhenValueMatches( 'data', true )

end

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

function KSPromisesTest.testUsingDoNoProcessFn( test )

	-- not sure if this is a realistic test case
	-- also it contains an invalid call to increase code coverage
	
	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local obj = {
		_state = 'open',
		close = function( self ) self._state = 'closed' end
	}
	
	local resourceP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', obj ) )
		
	local function releaseFnP( rsrc )
		rsrc:close()
		-- don't return a promise here to hit another code path in usingDo_promiseOrNothing
	end
		
	-- pass in incorrect number of parameters
	assert( not pcall( resourceP.usingDoP, resourceP, releaseFnP, releaseFnP, releaseFnP ), "test should fail, there should be 2 parameters" )

	local resultP = resourceP:usingDoP( releaseFnP, nil ) -- don't call process, just release when done
		
	resultP = resultP:mapP( function( status, value )
		assert( status == 'data', "Promise chain did not resolve successfully" )
		assert( value == true, "when no process function is supplied usingDoP returns true for it's data value" )
		assert( obj._state == 'closed', "The object should now be closed" )
		return 'data', true
	end )
	
	WFAsyncTestCase( test ):waitFor( resultP ):continueWhenValueMatches( 'data', true )

end

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

function KSPromisesTest.testWaitForP( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local initialP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', "Hello!" ) )
		
	local waited = false
		
	local function action( ... )
		local status, value = ...
		if status ~= 'data' or value ~= "Hello!" then
			debug.print( "Unexpected values to action" )
			test:notifyTestDidFail()
		end
		return WFIdle.laterP():mapP( function()
			waited = true
			return 'data', "Goodbye"
		end )
	end
	
	local testP = initialP:waitForP( action )
		:mapP( function( status, value )
			assert( status == 'data', "Bad status for waitForP result" )
			assert( value == 'Hello!', "Bad value for waitForP result" )
			assert( waited, "We failed to wait" )
			return 'data', true
		end )
	
	WFAsyncTestCase( test )
		:waitFor( testP )
		:continueWhenValueMatches( 'data', true )

end

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

function KSPromisesTest.testWaitForPWithError( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local initialP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', "Hello!" ) )
		
	local waited = false
		
	local function action( ... )
		waited = true
		local status, value = ...
		if status ~= 'data' or value ~= "Hello!" then
			debug.print( "Unexpected values to action" )
			test:notifyTestDidFail()
		end
		error "The action fails!"
	end
	
	local testP = initialP:waitForP( action )
		:mapP( function( status, value )
			assert( status == 'data', "Bad status for waitForP result" )
			assert( value == 'Hello!', "Bad value for waitForP result" )
			assert( waited, "We failed to wait" )
			return 'data', true
		end )
	
	WFAsyncTestCase( test )
		:waitFor( testP )
		:continueWhenValueMatches( 'data', true )

end

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

function KSPromisesTest.testWaitForPWithNoReturnValue( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'

	local initialP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', "Hello!" ) )

	local function actionWithoutReturnValue( ... )
		local status, value = ...
		if status ~= 'data' or value ~= "Hello!" then
			debug.print( "Unexpected values to action" )
			test:notifyTestDidFail()
		end
		-- don't return anything
	end

	local testP = initialP:waitForP( actionWithoutReturnValue )
		:mapP( function( status, value )
			assert( status == 'data', "Bad status for waitForP result" )
			assert( value == 'Hello!', "Bad value for waitForP result" )
			return 'data', true
		end )

	WFAsyncTestCase( test )
		:waitFor( testP )
		:continueWhenValueMatches( 'data', true )

end

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

function KSPromisesTest.testWaitForPWithIncorrectReturnValue( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'

	local initialP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', "Hello!" ) )

	local function actionWithIncorrectReturnValue( ... )
		local status, value = ...
		if status ~= 'data' or value ~= "Hello!" then
			debug.print( "Unexpected values to action" )
			test:notifyTestDidFail()
		end

		return 'error', false  -- should return a promise not data or error directly, these will be ignored
	end

	local testP = initialP:waitForP( actionWithIncorrectReturnValue )
		:mapP( function( status, value )
			assert( status == 'data', "Bad status for waitForP result" )
			assert( value == 'Hello!', "Bad value for waitForP result" )
			return 'data', true
		end )

	WFAsyncTestCase( test )
		:waitFor( testP )
		:continueWhenValueMatches( 'data', true ) -- above return values error, false were ignored

end

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

function KSPromisesTest.testWaitForPWithPromiseError( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local initialP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', "Hello!" ) )
		
	local waited = false
		
	local function action( ... )
		local status, value = ...
		if status ~= 'data' or value ~= "Hello!" then
			debug.print( "Unexpected values to action" )
			test:notifyTestDidFail()
		end
		return WFIdle.laterP():mapP( function()
			waited = true
			return 'error', "Action failure"
		end )
	end
	
	local testP = initialP:waitForP( action )
		:mapP( function( status, value )
			assert( status == 'data', "Bad status for waitForP result" )
			assert( value == 'Hello!', "Bad value for waitForP result" )
			assert( waited, "We failed to wait" )
			return 'data', true
		end )
	
	WFAsyncTestCase( test )
		:waitFor( testP )
		:continueWhenValueMatches( 'data', true )

end

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

function KSPromisesTest.testWaitForPWithPromise( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local initialP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', "Hello!" ) )
		
	local waited = false
	
	local action = WFIdle.laterP():mapP( function()
		waited = true
		return 'data', "Greetings!"
	end )
	
	local testP = initialP:waitForP( action )
		:mapP( function( status, value )
			assert( status == 'data', "Bad status for waitForP result" )
			assert( value == 'Hello!', "Bad value for waitForP result" )
			assert( waited, "We failed to wait" )
			return 'data', true
		end )
	
	WFAsyncTestCase( test )
		:waitFor( testP )
		:continueWhenValueMatches( 'data', true )

end

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

function KSPromisesTest.testObserveP( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local initialP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', "Hello!" ) )
		
	local observedStatus, observedValue
	
	local observedP = initialP:observeP( function( ... )
		observedStatus, observedValue = ...
	end )
	
	local comparedP = observedP:mapP( function( ... )
		if observedStatus == 'data' and observedValue == "Hello!" then
			return 'data', "Greetings!"
		end
		return ...
	end )
	
	WFAsyncTestCase( test )
		:waitFor( comparedP )
		:continueWhenValueMatches( 'data', "Greetings!" )

end

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

function KSPromisesTest.testObserveWithErrorP( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local initialP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'error', "Uh oh!" ) )
		
	local observedStatus, observedValue
	
	local observedP = initialP:observeP( function( ... )
		observedStatus, observedValue = ...
	end )
	
	local comparedP = observedP:mapP( function( ... )
		if observedStatus == 'error' and observedValue == "Uh oh!" then
			return 'data', "Not so good"
		end
		return ...
	end )
	
	local observedP2 = comparedP:observeP( function( ... )
		error "Just being snarky!"
	end )
	
	WFAsyncTestCase( test )
		:waitFor( observedP2 )
		:continueWhenValueMatches( 'data', "Not so good" )

end

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

function KSPromisesTest.testObservePWithFunctionTable( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	local WFAsyncTestCase = require 'WFAsyncTestCase'
	
	local initialP = WFIdle.laterP()
		:mapDataP( KSPromises.resolvedPromiseP( 'data', "Hello!" ) )
		
	local observedStatus, observedValue
	
	local observedP = initialP:observeP{
		data = function( status, value )
			observedStatus = status
			observedValue = value
		end,
		error = function( status, value )
			observedStatus = status
			observedValue = value
		end }
	
	local comparedP = observedP:mapP( function( ... )
		if observedStatus == 'data' and observedValue == "Hello!" then
			return 'data', "Greetings!"
		end
		return ...
	end )
	
	WFAsyncTestCase( test )
		:waitFor( comparedP )
		:continueWhenValueMatches( 'data', "Greetings!" )

end

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

function KSPromisesTest.testMapArrayP( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local function testP( x )
	
		local r = x + 1
		
		local m = x % 3
		
		if m == 0 then
			return r
		elseif m == 1 then
			return KSPromises.resolvedPromiseP( 'data', r )
		else
			return WFIdle.onIdleP():mapDataP( function()
				return 'data', r
			end )
		end
	
	end

	local mapArrayP = KSPromises.mapArrayP(
				testP,
				{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
				2 )
				
	local resultP = mapArrayP:mapDataP( function( _, value )
		for i = 1, 10 do
			if i + 1 ~= value[ i ] then
				return 'error',
					string.format( "Bad value at index %s: %s",
						tostring( i ), tostring( value[ i ] ) )
			end
		end
		return 'data', true
	end )

	delayResultP( resultP ):whenResolvedDo( function( status, value )
		assert( status == "data" )
		test:notifyTestDidSucceed()
	end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )

end

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

function KSPromisesTest.testMapArrayPWithMapToCanceled( test )

	local KSPromises = require 'KSPromises'
	
	local function testP( x )
	
		local r = x + 1
		
		if x == 8 then
			return KSPromises.canceledPromiseP()
		else
			return r
		end
	
	end

	local mapArrayP = KSPromises.mapArrayP(
				testP,
				-- using a promise hits more code in mapArrayP
				KSPromises.resolvedPromiseP( 'data', { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } ),
				2 )
				
	local wasResolved = false

	mapArrayP:whenResolvedDo( function( status, value )
		assert( status == nil, "mapArrayP should be canceled" )
		wasResolved = true
	end ):runUntilDone()

	assert( wasResolved, "promise wasn't resolved immediately" )

end

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

function KSPromisesTest.testMapArrayPWithMapToErrorValue( test )

	local KSPromises = require 'KSPromises'
	
	local function testP( x )
	
		local r = x + 1
		
		if x == 8 then
			return KSPromises.resolvedPromiseP( 'error', r )
		else
			return r
		end
	
	end

	local mapArrayP = KSPromises.mapArrayP(
				testP,
				{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
				2 )
				

	local wasResolved = false

	mapArrayP:whenResolvedDo( function( status, value )
		assert( status == 'error', "mapArrayP should return status of error" )
		wasResolved = true
	end ):runUntilDone()

	assert( wasResolved, "promise wasn't resolved immediately" )
	
end

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

function KSPromisesTest.testMapArrayPWithError( test )

	local KSPromises = require 'KSPromises'
	
	local function testP( x )
	
		local r = x + 1
		
		if x == 8 then
			error "We don't like 8 in testMapArray"
		else
			return r
		end
	
	end

	local mapArrayP = KSPromises.mapArrayP(
				testP,
				{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
				2 )
				

	local wasResolved = false

	mapArrayP:whenResolvedDo( function( status, value )
		assert( status == 'error', "mapArrayP should return status of error" )
		wasResolved = true
	end ):runUntilDone()

	assert( wasResolved, "promise wasn't resolved immediately" )
	
end

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

function KSPromisesTest.testMapArrayPChangePriority( test )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local function testP( x )
	
		return WFIdle.onIdleP():mapDataP( function()
			return 'data', x + 1
		end )
	
	end

	local mapArrayP = KSPromises.mapArrayP(
				testP,
				{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
				2 )
				
	local promiseAction = mapArrayP:whenResolvedDo( function( status, value )
		for i = 1, 10 do
			if i + 1 ~= value[ i ] then
				return 'error',
					string.format( "Bad value at index %s: %s",
						tostring( i ), tostring( value[ i ] ) )
			end
		end

		test:notifyTestDidSucceed()
	end )
	
	promiseAction:runUntilDone()

	promiseAction:setPriority(2) -- hits a line in KSParallelResolver:startResolution

	test:waitForTestToFinish( TEST_TIMEOUT )
	
end

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

function KSPromisesTest.testPromiseActionSetDefaultPriority()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local wasResolved = false
	
	local promiseAction = promise:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
		wasResolved = true
	end )
	
	promiseAction:setPriority( 'default' )
	
	promiseAction:runUntilDone()
	
	resolver( 'data', 5 )

	assert( wasResolved, "promise should be resolved" )
	
end

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

-- @mark untilResolvedB

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

function KSPromisesTest.testUntilResolvedB_immediateData( test )

	local KSPromises = require 'KSPromises'

	local p = KSPromises.resolvedPromiseP( 'data', "Greetings", "Salutations" )
	
	local b = p:untilResolvedB( 'data', "Not Yet!" )
	
	KSTestUtils.assertMatchArgs( "Greetings", "Salutations" )( b:valueNow() )

end

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

function KSPromisesTest.testUntilResolvedB_immediateError( test )

	local KSPromises = require 'KSPromises'

	local p = KSPromises.resolvedPromiseP( 'error', "Not so fast" )
	
	local b = p:untilResolvedB( 'data', "Not Yet!" )
	
	KSTestUtils.assertMatchArgs( true, "Not so fast" )( b:isErrorNow() )

end

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

local requestTestHandler = WFWorkerThread( 'WFRequestTestHandler.lua' )

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

function KSPromisesTest.testUntilResolvedBWithRequest( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()

			local p = requestTestHandler:requestP( 'echo', "The data value" )
			
			local b = p:untilResolvedB( 'data', "Not Yet!" )
			
			assert( b:valueNow() == "Not Yet!", "Value should be 'Not Yet!'" )
			
			return b
		
		end )
		:continueWhenValueMatches( 1, "The data value" )
	
end

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

function KSPromisesTest.testUntilResolvedBWithRequestError( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			local p = requestTestHandler:requestP( 'sendError', "Failure!" )
			
			return p:untilResolvedB( 'data', "Not Yet!" )
		
		end )
		:continueWhenValueMatchesError( 'Failure!' )
	
end

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

function KSPromisesTest.testUntilResolvedB_initialError( test )

	WFAsyncTestCase( test )
	
		:waitFor( function()

			local p = requestTestHandler:requestP( 'echo', "The data value" )
			
			local b = p:untilResolvedB( 'error', "Not Yet!" )
			
			local isError, msg = b:isErrorNow()
			
			assert( isError, "Behavior should be error valued." )
			
			assert( msg == "Not Yet!", "Error message should be correct" )
			
			return b
		
		end )
		:continueWhenValueMatches( 1, "The data value" )
	
end


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

function KSPromisesTest.testUntilResolvedB_invalidParamsImmediate( test )

	local KSPromises = require 'KSPromises'

	local p = KSPromises.resolvedPromiseP( 'invalid', "Bad Params" )
	
	assert( not pcall( p.untilResolvedB, p, 'data', "Hello" ), "should fail, first param should be 'data' or 'error'")
	
end

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

function KSPromisesTest.testUntilResolvedB_invalidParams( test )

	local KSPromises = require 'KSPromises'

	local p = KSPromises.promiseP( )
	
	assert( not pcall( p.untilResolvedB, p, 'invalid', "Bad Params" ), "should fail, first param should be 'data' or 'error'")
	
end

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

function KSPromisesTest.testArgTupleP_immediate()

	local KSPromises = require 'KSPromises'

	local aP = KSPromises.resolvedPromiseP( 'data', 3, 4 )
	
	local bP = KSPromises.resolvedPromiseP( 'data', 'a', 'b' )
	
	local rP = KSPromises.argTupleP( aP, bP )
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 3, "There should be 3 data values." )
		local x, y, z = ...
		assert( x == 3 and y == 'a' and z == 'b',
			string.format( "Wrong result constructed: %s, %s, %s",
							tostring( x ), tostring( y ), tostring( z ) ) )
		isResolved = true
	end ):runUntilDone()
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testArgTupleP_deferred()

	local KSPromises = require 'KSPromises'

	local aP, aR = KSPromises.promiseP()
	
	local bP = KSPromises.resolvedPromiseP( 'data', 'a', 'b' )
	
	local rP = KSPromises.argTupleP( aP, bP )
	
	rP:runUntilDone()
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 3, "There should be 3 data values." )
		local x, y, z = ...
		assert( x == 3 and y == 'a' and z == 'b',
			string.format( "Wrong result constructed: %s, %s, %s",
							tostring( x ), tostring( y ), tostring( z ) ) )
		isResolved = true
	end ):runUntilDone()
	
	assert( not isResolved, "The promise should not have resolved yet" )
	
	aR( 'data', 3, 4 )
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testArgTupleP_immediateError()

	local KSPromises = require 'KSPromises'

	local aP = KSPromises.resolvedPromiseP( 'error', "Oops!" )
	
	local bP = KSPromises.resolvedPromiseP( 'data', 'a', 'b' )
	
	local rP = KSPromises.argTupleP( aP, bP )
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'error', "We should have resolved as error." )
		assert( select( '#', ... ) == 1, "There should be 1 data value." )
		assert( ( ... ) == "Oops!", "Wrong error message." )
		isResolved = true
	end ):runUntilDone()
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testArgTupleP_deferredError()

	local KSPromises = require 'KSPromises'

	local aP, aR = KSPromises.promiseP()
	
	local bP = KSPromises.resolvedPromiseP( 'data', 'a', 'b' )
	
	local rP = KSPromises.argTupleP( aP, bP )
	
	rP:runUntilDone()
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'error', "We should have resolved as error." )
		assert( select( '#', ... ) == 1, "There should be 1 data value." )
		assert( ( ... ) == "Oops!", "Wrong error message." )
		isResolved = true
	end ):runUntilDone()
	
	assert( not isResolved, "The promise should not have resolved yet" )
	
	aR( 'error', "Oops!" )
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testCallP_sum()

	local KSPromises = require 'KSPromises'
	
	local function sum( ... )
		local total = 0
		for i = 1, select( '#', ... ) do
			local x = select( i, ... )
			total = total + x
		end
		return total
	end
	
	local aP, aR = KSPromises.promiseP()
	
	local bP, bR = KSPromises.promiseP()
	
	local rP = KSPromises.callP( sum, aP, bP )
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 1, "There should be 1 data value." )
		assert( ( ... ) == 8, "The total should have been 8." )
		isResolved = true
	end ):runUntilDone()
	
	assert( not isResolved, "The promise should not have resolved yet" )
	
	aR( 'data', 1, 2 )
	bR( 'data', 3, 4 )
		-- sum gets 1, 3, 4
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testCallP_sumUsingPromisedFunction()

	local KSPromises = require 'KSPromises'
	
	local function sum( ... )
		local total = 0
		for i = 1, select( '#', ... ) do
			local x = select( i, ... )
			total = total + x
		end
		return total
	end
	
	local sP, sR = KSPromises.promiseP()

	local aP, aR = KSPromises.promiseP()
	
	local bP, bR = KSPromises.promiseP()
	
	local rP = KSPromises.callP( sP, aP, bP )
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 1, "There should be 1 data value." )
		assert( ( ... ) == 8, "The total should have been 8." )
		isResolved = true
	end ):runUntilDone()
	
	assert( not isResolved, "The promise should not have resolved yet" )
	
	sR( 'data', sum )
	aR( 'data', 1, 2 )
	bR( 'data', 3, 4 )
		-- sum gets 1, 3, 4
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testCallP_sumImmediate()

	local KSPromises = require 'KSPromises'
	
	local function sumAndCount( ... )
		local total = 0
		for i = 1, select( '#', ... ) do
			local x = select( i, ... )
			total = total + x
		end

		-- return 2 values here to hit another code path in argTuple
		return total, select( '#', ... )
	end
	
	local aP = KSPromises.resolvedPromiseP( 'data', 1, 2 )
	
	local bP = KSPromises.resolvedPromiseP( 'data', 3, 4 )
	
	local rP = KSPromises.callP( sumAndCount, aP, bP ) -- calling with all promises resolved hits another code path in argTuple
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 2, "There should be 2 data values." )
		assert( select( 1, ... ) == 8, "The total should have been 8." )
		assert( select( 2, ... ) == 3, "The total should have been 3." )
		isResolved = true
	end ):runUntilDone()
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testCallP_sumImmediateWithPromiseResults()

	local KSPromises = require 'KSPromises'
	
	local function sumAndCount( ... )
		local total = 0
		for i = 1, select( '#', ... ) do
			local x = select( i, ... )
			total = total + x
		end

		-- return 2 promise values here to hit another code path in argTuple
		return KSPromises.resolvedPromiseP( 'data', total ), KSPromises.resolvedPromiseP( 'data', select( '#', ... ) )
	end
	
	local aP = KSPromises.resolvedPromiseP( 'data', 1, 2 )
	
	local bP = KSPromises.resolvedPromiseP( 'data', 3, 4 )
	
	local rP = KSPromises.callP( sumAndCount, aP, bP )
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 2, "There should be 2 data values." )
		assert( select( 1, ... ) == 8, "The total should have been 8." )
		assert( select( 2, ... ) == 3, "The total should have been 3." )
		isResolved = true
	end ):runUntilDone()
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testCallP_chain()

	local KSPromises = require 'KSPromises'
	
	local cR = nil
	
	local function chain( ... )
		local cP
		cP, cR = KSPromises.promiseP()
		return "Greetings!", cP
	end
	
	local aP, aR = KSPromises.promiseP()
	
	local bP, bR = KSPromises.promiseP()
	
	local rP = KSPromises.callP( chain, aP, bP )
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 3, "There should be 2 data values." )
		local x, y, z = ...
		assert( x == "Greetings!" and y == "Hello!" and z == "Salutations!", "Wrong results." )
		isResolved = true
	end ):runUntilDone()
	
	assert( not isResolved, "The promise should not have resolved yet" )
	
	aR( 'data', 1, 2 )
	bR( 'data', 3, 4 )
		-- sum gets 1, 3, 4
	
	assert( not isResolved, "Should not be resolved yet." )
	
	cR( 'data', "Hello!", "Salutations!" )

	assert( isResolved, "Should now be resolved." )
	
end

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

function KSPromisesTest.testCallP_error()

	local KSPromises = require 'KSPromises'
	
	local dR = nil
	
	local function chain( ... )
		local dP
		dP, dR = KSPromises.promiseP()
		return "Greetings!", dP
	end
	
	local aP, aR = KSPromises.promiseP()
	
	local bP, bR = KSPromises.promiseP()
	
	local cP, cR = KSPromises.promiseP()
	
	local rP = KSPromises.callP( chain, aP, bP, cP )
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'error', "We should have resolved as error." )
		assert( select( '#', ... ) == 1, "There should be 1 data value." )
		assert( ( ... ) == "Oops!", "Wrong error message." )
		isResolved = true
	end ):runUntilDone()
	
	assert( not isResolved, "The promise should not have resolved yet" )
	
	aR( 'data', 1, 2 )
	
	assert( not isResolved, "The promise should still not have resolved yet" )
	
	bR( 'error', "Oops!" )
	
	assert( isResolved, "Should now be resolved." )
	
	assert( dR == nil, "We should not have a sub-resolver" )
	
	cR( 'data', 3, 4 )
	
end

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

function KSPromisesTest.testCallP_empty()

	local KSPromises = require 'KSPromises'
	
	local functionCalled = false
	
	local function fn( ... )
		functionCalled = true
		-- test not returning anything to increase code coverage
	end
	
	local rP = KSPromises.callP( fn, "foo", "bar" )
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 0, "data should be empty" )
		isResolved = true
	end ):runUntilDone()
	
	assert( functionCalled, "should have called the fn in callP" )
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testCallP_oneValue()

	local KSPromises = require 'KSPromises'

	local function increment( ... )
		assert( select( '#', ... ) == 1, "function should be 42" )
		return select( 1, ... ) + 1
	end
	
	local rP = KSPromises.callP( increment, 41 ) -- test with one value (not promise) to hit another code path within argTuple
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 1, "data have one value." )
		assert( select( 1, ... ) == 42, "data should be incremented to 42" )
		isResolved = true
	end ):runUntilDone()
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testCallP_noPromises()

	local KSPromises = require 'KSPromises'

	local function sum( ... )
		local total = 0
		for i = 1, select( '#', ... ) do
			local x = select( i, ... )
			total = total + x
		end
		return total
	end
	
	local rP = KSPromises.callP( sum, 1, 2, 3 ) -- don't use any promises to go down a different code path in argTuple
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 1, "data have one value." )
		assert( select( 1, ... ) == 6, "value should be 6" )
		isResolved = true
	end ):runUntilDone()
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testCallP_oneP()

	local KSPromises = require 'KSPromises'

	local function increment( ... )
		assert( select( '#', ... ) == 1, "function should be 42" )
		return select( 1, ... ) + 1
	end
	
	local aP, aR = KSPromises.promiseP()

	local rP = KSPromises.callP( increment, aP ) -- test with one promise to hit another code path within argTuple
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 1, "data have one value." )
		assert( select( 1, ... ) == 42, "data should be incremented to 42" )
		isResolved = true
	end ):runUntilDone()
	
	aR( 'data', 41 )
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testCallP_canceledP()

	local KSPromises = require 'KSPromises'

	local function fn( ... )
		assert( false, "shouldn't call function with canceled promise" )
	end
	
	local aP, aR = KSPromises.promiseP()

	local bP, bR = KSPromises.promiseP()
	bR( KSPromises.canceledPromiseP() ) -- cancel one immediately to hit different code path in argTuple

	local rP = KSPromises.callP( fn, aP, bP )
	
	local wasCanceled = false
	
	rP:whenResolvedDo( function( status, ... )
		wasCanceled = select( '#', ... ) == 0
	end ):runUntilDone()
	
	aR( 'data', 41 )
	
	assert( wasCanceled, "The promise failed to resolve as canceled." )
	
	assert( rP:isCanceled() )

end

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

function KSPromisesTest.testTableP()

	local KSPromises = require 'KSPromises'
	local KSTableUtils = require 'KSTableUtils'
	
	local aP, aR = KSPromises.promiseP()
	
	local bP, bR = KSPromises.promiseP()
	
	local rP = KSPromises.tableP {
		x = aP,
		y = bP,
		z = 7,
		w = { foo = aP }
	}
	
	local isResolved = false
	
	rP:whenResolvedDo( function( status, ... )
		assert( status == 'data', "We should have resolved as data." )
		assert( select( '#', ... ) == 1, "There should be 1 data value." )
		local v = ( ... )
		assert( KSTableUtils.deepEquals( v, { x = 1, y = 3, z = 7, w = { foo = 1 } } ),
			"Result is wrong" )
		isResolved = true
	end ):runUntilDone()
	
	assert( not isResolved, "The promise should not have resolved yet" )
	
	aR( 'data', 1, 2 )
	bR( 'data', 3, 4 )
	
	assert( isResolved, "The promise failed to resolve properly." )

end

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

function KSPromisesTest.testTablePContainingEvent()

	local KSPromises = require 'KSPromises'
	local KSReactive = require 'KSReactive'
	
	local aP, _ = KSPromises.promiseP()
	
	local zeroE = KSReactive.zeroE()
	
	KSTestUtils.assertFails( KSPromises.tableP, {
			x = aP,
			y = zeroE, -- event nodes aren't allowed in tableP
			z = 7,
		}
	)

end

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

function KSPromisesTest.testDebugP()

	-- test doesn't validate debugP output to console but does execute the lines
	-- to increase code coverage and validates that it doesn't crash

	local KSPromises = require 'KSPromises'

	local promise, resolver = KSPromises.promiseP()

	promise = promise:debugP('MyDebugPromiseTestName')

	local resolvedValue = nil

	promise:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
		local _, value = ...
		resolvedValue = value
	end ):runUntilDone()

	assert( resolvedValue == nil, "Resolved value should still be nil" )

	resolver( 'data', 5 )

	assert( resolvedValue == 5, "Resolved value should now be 5." )

end

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

function KSPromisesTest.testDebugPCanceled()

	-- test doesn't validate debugP output to console but does execute the lines
	-- to increase code coverage and validates that it doesn't crash

	local KSPromises = require 'KSPromises'

	local promise, resolver = KSPromises.promiseP()

	promise = promise:debugP('MyDebugPromiseTestName')

	local wasCanceled = false

	promise:whenResolvedDo( function( ... )
		wasCanceled = select( '#', ... ) == 0
	end ):runUntilDone()
	
	resolver( KSPromises.canceledPromiseP() )

	assert( wasCanceled, "promise should have been resolved as canceled" )

end

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

local function makeRecursivePromiseP( n )

	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local counter = n
	
	local p
	
	local function makePromiseP()
	
		-- The following code sniffs inside of the promise logic to check
		-- the chain length. If the test fails, it could be because we changed
		-- the implementation
	
		local depth = 0
		
		local q = p
		
		while q do
			depth = depth + 1
			q = q._source
		end
		
		assert( depth < 5, "We should not have accumulated a long chain of promise nodes." )
	
		if counter < 1 then
			return KSPromises.resolvedPromiseP( 'data', true )
		end
		
		counter = counter - 1
		
		return WFIdle.laterP():mapDataP( function()
			return makePromiseP()
		end )
	
	end
	
	p = makePromiseP()
	
	return p
	
end

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

function KSPromisesTest.testPromiseRecursionSmall( test )
	
	makeRecursivePromiseP( 10 ):whenResolvedDo( function( status, value )
			if status == 'data' and value == true then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )
	
end

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

function KSPromisesTest.testPromiseRecursionMedium( test )
	
	makeRecursivePromiseP( 100 ):whenResolvedDo( function( status, value )
			if status == 'data' and value == true then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )
	
end

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

function KSPromisesTest.testRepeatUntilP( test )
	
	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local counter = 0
	local sum = 0
	
	local function makeStepP()
		return WFIdle.laterP()
		:mapP( function()
			counter = counter + 1
			sum = sum + counter
			return 'data', counter, sum
		end )
		:mapDataP( function( status, ... )
			if 10 == ( ... ) then
				return status, ...
			else
				return 'data', false
			end
		end )
	end
	
	KSPromises.repeatUntilP( makeStepP ):whenResolvedDo( function( status, value1, value2 )
			if status == 'data' and value1 == 10 and value2 == 55 then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )
	
end

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

function KSPromisesTest.testLiftP( test )
	
	local KSPromises = require 'KSPromises'
	local WFIdle = require 'WFIdle'
	
	local fnP = KSPromises.liftP( function( a, b ) return a + b end )
	
	local aP = WFIdle.laterP():mapP( function() return 'data', 3 end )
	local bP = aP:waitForP( WFIdle.laterP ):mapP( function() return 'data', 7 end )
	
	local cP = fnP( aP, bP )
	
	cP:whenResolvedDo( function( status, value )
			if status == 'data' and value == 10 then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	
	test:waitForTestToFinish( TEST_TIMEOUT )
	
end

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

function KSPromisesTest.testMapDataP_new_dataToData()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2 = promise:mapDataP_new( function( ... )
		KSTestUtils.assertMatchArgs( 5 )( ... )
		return ( ... ) + 1
	end )
	
	local resolvedValue = nil
	
	promise2:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 6 )( ... )
		local _, value = ...
		resolvedValue = value
	end ):runUntilDone()
	
	assert( resolvedValue == nil, "Resolved value should still be nil" )
	
	resolver( 'data', 5 )
	
	assert( resolvedValue == 6, "Resolved value should now be 5." )

end

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

function KSPromisesTest.testMapDataP_new_dataToError()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2 = promise:mapDataP_new( function( ... )
		KSTestUtils.assertMatchArgs( 5 )( ... )
		error( "Catch" )
	end )
	
	local resolvedStatus = nil
	local resolvedMessage = nil
	
	promise2:whenResolvedDo( function( ... )
		resolvedStatus, resolvedMessage = ...
	end ):runUntilDone()
	
	assert( resolvedStatus == nil, "Resolved value should still be nil" )
	
	resolver( 'data', 5 )
	
	assert( resolvedStatus == 'error', "We should have generated an error." )
	assert( string.find( tostring( resolvedMessage ), "Catch" ),
			"We should have preserved the error message." )

end

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

function KSPromisesTest.testMapDataP_new_dataToPromises()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2 = promise:mapDataP_new( function( ... )
		KSTestUtils.assertMatchArgs( 5 )( ... )
		return KSPromises.resolvedPromiseP( 'data', 1, 2 ), 2, KSPromises.resolvedPromiseP( 'data', 3, 4 )
	end )
	
	local resolvedStatus = nil
	
	promise2:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 1, 2, 3, 4 )( ... )
		resolvedStatus = 'data'
	end ):runUntilDone()
	
	assert( resolvedStatus == nil, "Resolved value should still be nil" )
	
	resolver( 'data', 5 )
	
	assert( resolvedStatus == 'data', "We should have resolved as data." )

end

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

function KSPromisesTest.testMapDataP_new_dataToPromisedTables()

	local KSPromises = require 'KSPromises'
	local KSTableUtils = require 'KSTableUtils'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2 = promise:mapDataP_new( function( ... )
		KSTestUtils.assertMatchArgs( 5 )( ... )
		return {
			a = KSPromises.resolvedPromiseP( 'data', 1 ),
			b = KSPromises.resolvedPromiseP( 'data', 2 ),
			KSPromises.resolvedPromiseP( 'data', 3 ),
			KSPromises.resolvedPromiseP( 'data', 4 )
		}
	end )
	
	local resolvedStatus = nil
	
	promise2:whenResolvedDo( function( status, value )
		assert( status == 'data', "Should have resolved as data." )
		assert( KSTableUtils.deepEquals( value, { a = 1, b = 2, 3, 4 } ),
			"Resulting table is wrong." )
		resolvedStatus = 'data'
	end ):runUntilDone()
	
	assert( resolvedStatus == nil, "Resolved value should still be nil" )
	
	resolver( 'data', 5 )
	
	assert( resolvedStatus == 'data', "We should have resolved as data." )

end

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

function KSPromisesTest.testMapDataP_new_nonData()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2 = promise:mapDataP_new( function( ... )
		assert( false, "We should not have gotten here" )
	end )
	
	local resolvedStatus, resolvedValue
	
	promise2:whenResolvedDo( function( ... )
		resolvedStatus, resolvedValue = ...
	end ):runUntilDone()
	
	resolver( 'flerb', 5 )
		-- This should bypass the mapping
		
	assert( resolvedStatus == 'flerb' and resolvedValue == 5,
			"Unexpected resolved results." )

end

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

function KSPromisesTest.testResolveViaPromise()

	local KSPromises = require 'KSPromises'
	
	local promise, resolver = KSPromises.promiseP()
	
	local promise2, resolver2
	
	local promise3 = promise:mapP{ data = function( ... )
		KSTestUtils.assertMatchArgs( 'data', 5 )( ... )
		promise2, resolver2 = KSPromises.promiseP()
		return promise2
	end }
	
	local resolvedValue = nil
	
	promise3:whenResolvedDo( function( ... )
		KSTestUtils.assertMatchArgs( 'data', 7 )( ... )
		local _, value = ...
		resolvedValue = value
	end ):runUntilDone()
	
	assert( resolvedValue == nil, "Resolved value should still be nil" )
	
	resolver( 'data', 5 )
	
	assert( resolvedValue == nil, "Resolved value should still be nil (2)" )
	
	resolver2( 'data', 7 )
	
	assert( resolvedValue == 7, "Resolved value should now be 7." )

end

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

function KSPromisesTest.testStallTransforms( test )
	
	local WFIdle = require 'WFIdle'
	
	local p1 = WFIdle.laterP()
	
	local didFire = false
	
	p1:whenResolvedDo( function() didFire = true end ):runUntilDone()
	
	local p2 = p1:mapP( WFIdle.laterP )
	
	local executed = false
	
	local q1 = p1:mapDataP( function( ... )
		executed = true
		return 'data', "Hello!"
	end )
	
	-- We activate the promise to begin with
	
	local a1a = q1:runUntilDone()
	
	-- When p2 fires, we want to check that we did fire but have
	-- not yet executed. We will then reactivate q1.
	
	local a1b = nil
	
	p2:whenResolvedDo( function()
		if executed then
			debug.print( "We should not have executed the action yet" )
			test:notifyTestDidFail()
		end
		if not didFire then
			debug.print( "We should have fired p1" )
			test:notifyTestDidFail()
		end
		a1b = q1:whenResolvedDo( function( status, value )
			if status == 'data' and value == "Hello!" then
				test:notifyTestDidSucceed()
			else
				test:notifyTestDidFail()
			end
		end ):runUntilDone()
	end ):runUntilDone()
	
	-- Now close off the original action for the promise
	
	assert( not executed, "We should not have executed yet" )
	
	assert( not didFire, "We should not have fired yet" )
	
	a1a:close()
	
	a1a = nil
	
	test:waitForTestToFinish( TEST_TIMEOUT )
	
end

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

return KSPromisesTest
