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

WFPerformanceReportTest.lua

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

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

NOTICE: All information contained herein is, and remains 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 are protected by all applicable
intellectual property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

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

local WFPlatformInfo = require 'WFPlatformInfo'

if WFPlatformInfo.isiOS then
	return {}
end

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

local Rx = require 'Rx'

	-- WF.core.performance
local KSTableUtils = require "KSTableUtils"

	-- WF.core.performance
local WFPerformanceReport = require 'WFPerformanceReport'
local WFPerformanceReportTestUtils = require "WFPerformanceReportTestUtils"

	-- WF.core.rxlua
local WFRxRecordObservable = require 'WFRxRecordObservable'
local WFRxScheduler = require 'WFRxScheduler'

	-- WF.core.self_test
local WFRxTestCase = require 'WFRxTestCase'

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

local recordObservable = WFRxRecordObservable

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

local WFPerformanceReportTest = {}

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

function WFPerformanceReportTest.setUp()
	WFPerformanceReport.clearPerfReport()
end

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

local function countEvents( events )

	if type( events ) ~= 'table' then return events end

	return #events

end

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

--[[

local function convertSerializedEventsToTable( events )

	local messagesString = events[2] --events[1] is always the string ("message")
									 --events[2] is the serialized table that contains
									 --'kind' and 'messages' keys
	
	return loadstring( "return " .. messagesString )().messages --convert serialized table to table

end

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

local function mapRetrieveResultsFromSerializedTable( events )

	if type( events ) ~= 'table' then return events end

	if events == {} then return 'empty' end
	
	events = convertSerializedEventsToTable( events )
	
	for i, doc in ipairs( events ) do
		if doc.key and type( doc.key ) == 'string' then
			events[ i ].key = 'key'
		end
		if doc.value and type( doc.value ) == 'table' then
			events[ i ].value = 'value'
		end
	end

	return events

end
]]--

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

local function mapRetrieveResults( events )

	if type( events ) ~= 'table' then return events end

	if events == {} then return 'empty' end

	for i, doc in ipairs( events ) do
		if doc.key and type( doc.key ) == 'string' then
			events[ i ].key = 'key'
		end
		if doc.value and type( doc.value ) == 'table' then
			events[ i ].value = 'value'
		end
	end

	return events

end

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

local function checkLogsTable( events )

	if type( events ) ~= 'table' then return events end

	if events == {} then return 'empty' end

	for _, doc in ipairs( events ) do
		if doc.value and type( doc.value ) == 'table' then
			-- checks the log table should contain at least 3 entries 
			-- two for timestamp and timeIntervalSinceAppLaunch 
			-- and third for isUnitTest value. 
			local nLogs = 0
  			for _ in pairs( doc.value ) do nLogs = nLogs + 1 end
			if nLogs < 3 then 
				return false
			end
		end
	end

	return true

end

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

local function checkObservableValueInTable( events )

	if type( events ) ~= 'table' then 
	
		return events 
		
	end

	if not next( events ) then 

		return 'empty log table' 
		
	end

	local sawObservableValue = false

	for _, doc in ipairs( events ) do

		if string.find( doc.key, "onNext") then 

			local metricsTable = doc.value

			if metricsTable and type( metricsTable ) == 'table' then

				if not metricsTable[ 'observableValue' ] then 

					return "observableValue is missing"

				else

					sawObservableValue = true

				end

			else 

				return "metrics table was missing"

			end

		end

	end

	return sawObservableValue and 
				"observableValue is present" or
				"oNext never fired"

end

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

local function generateExpectedResult( nEvents )

	local results = {}

	for i = 1, nEvents do
		results[ i ] = { key = "key", value = "value" }
	end

	return results

end

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

function WFPerformanceReportTest.rxTestMultipleEvents( rxTest )

	rxTest:setTimeLimit( 30 )

	local src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndCompleted()

	local o = rxTest:observe( src:call( recordObservable, "rxTestMultipleEventsAndCompleted" ) )
	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectCompleted()

	rxTest:expectSingleValue( WFPerformanceReport.retrieve( true ):map( countEvents ), nEvents )

end


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

WFPerformanceReportTest.rxTestMultipleEvents =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestMultipleEvents )
		
--------------------------------------------------------------------------------

function WFPerformanceReportTest.rxTestMultipleEventsAndCompleted( rxTest )

	rxTest:setTimeLimit( 30 )

	local src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndCompleted()
	local optTable = {}
	optTable[ 'onCompleted' ] = true
	local o = rxTest:observe( src:call( recordObservable, "rxTestMultipleEventsAndCompleted", optTable ) )
	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectCompleted()

	rxTest:expectSingleValue( WFPerformanceReport.retrieve( true ):map( countEvents ), nEvents + 1 ) 
		-- +1 for onCompleted event which is recorded only when flag is passed in optTable

end

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

WFPerformanceReportTest.rxTestMultipleEventsAndCompleted =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestMultipleEventsAndCompleted )

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

function WFPerformanceReportTest.rxTestMultipleEventsAndError( rxTest )

	rxTest:setTimeLimit( 30 )

	local src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndError()

	local o = rxTest:observe( src:call( recordObservable, "rxTestMultipleEventsAndError " ) )
	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectError( "ExpectedError" )

	rxTest:expectSingleValue( WFPerformanceReport.retrieve( true ):map( countEvents ), nEvents + 1 )
																		-- +1 accounts for onError event
end

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

WFPerformanceReportTest.rxTestMultipleEventsAndError =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestMultipleEventsAndError )

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

function WFPerformanceReportTest.rxTestClearEvents( rxTest )

	rxTest:setTimeLimit( 30 )

	local src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndError()

	local o = rxTest:observe( src:call( recordObservable, "rxTestMultipleEventsAndError " ) )
	for i = 1, nEvents do
		o:expectData( i )
	end

	o:expectError( "ExpectedError" )

	rxTest:expectSingleValue( WFPerformanceReport.retrieve( true ):map( mapRetrieveResults ),
							  generateExpectedResult( nEvents + 1 ) ) -- +1 accounts for onError event too

	rxTest:expectSingleValue( WFPerformanceReport.retrieve( false ), {} )

end

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

WFPerformanceReportTest.rxTestClearEvents =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestClearEvents )

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

function WFPerformanceReportTest.rxTestIsDaemonIdle( rxTest )

	rxTest:setTimeLimit( 10 )
	
	--default should be false
	local daemonIdle = rxTest:observe( WFPerformanceReport.isDaemonIdle() )
	daemonIdle:expectData( false ) 
	
	-- set true 
	WFPerformanceReport.setDaemonIdle( true )
	daemonIdle:expectData( true )	
	
	-- then set false again
	WFPerformanceReport.setDaemonIdle( false )	
	daemonIdle:expectData( false )
	
end

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

-- FIXME [jandrew 2019-03-22]: The EMP echo server appears to be a bit buggy and will
-- prematurely close RTMFP flows if the same peer connects more than once in a short
-- period of time. This causes unit test instability. Since we can't fix this on our end
-- I'm disable this test for now.

--[[
function WFPerformanceReportTest.rxTestSendPerfData( rxTest )

	rxTest:setTimeLimit( 60 )
	
	-- step one , generate few events
	local src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndCompleted()
	
	--step 2, record them
	local o1 = rxTest:observe( src:call( recordObservable, "rxTestSendPerfData" ) )
	for i = 1, nEvents do
        o1:expectData( i )
	end
	o1:expectCompleted()
	
	--step3 , send recorded events to echo server
	local sendData = WFPerformanceReport.sendPerfData( 2 )			
	local o = rxTest:observe( sendData )
	o:expectCompleted()
	
	--step4, listen to echoed stream 
	local echoStream = rxTest:observe( WFPerformanceReport.DEBUG_getEchoedStream():map( mapRetrieveResultsFromSerializedTable ) ) 
	echoStream:expectData( generateExpectedResult( nEvents ) )
	
end

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

WFPerformanceReportTest.rxTestSendPerfData =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestSendPerfData )

]]--

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

function WFPerformanceReportTest.rxTestOptionalLogsTable( rxTest )

	rxTest:setTimeLimit( 30 )

	local src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndCompleted()

	local o = rxTest:observe( src:call( recordObservable, "rxTestMultipleEventsAndCompleted" ) )
	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectCompleted()

	rxTest:expectSingleValue( WFPerformanceReport.retrieve( true ):map( checkLogsTable ), true )
	
	
	local tmpTable = { onNextCalled = 0,
						  onCompletedCalled = false,
						  onErrorCalled = false }
	local optionalPerfFunctions = { onNext = function() tmpTable.onNextCalled = tmpTable.onNextCalled + 1 end,
									onCompleted = function() tmpTable.onCompletedCalled = true end,
									onError = function() tmpTable.onErrorCalled = true end }
	
	o = rxTest:observe( src:call( recordObservable, "rxTestMultipleEventsAndCompleted", optionalPerfFunctions ) )										
	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectCompleted()
	
	assert( tmpTable.onNextCalled == nEvents , 'onNext perf handler not called' )
	assert( tmpTable.onCompletedCalled, 'onCompleted perf handler not called' )
	assert( tmpTable.onErrorCalled == false, 'onError perf handler should not have been called' )
	
	tmpTable.onNextCalled = 0
	tmpTable.onCompletedCalled = false
	tmpTable.onErrorCalled = false
	
	src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndError()
	o = rxTest:observe( src:call( recordObservable, "rxTestMultipleEventsAndError", optionalPerfFunctions ) )										
	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectError( "ExpectedError" )
	
	assert( tmpTable.onNextCalled == nEvents , 'onNext perf handler not called' )
	assert( tmpTable.onErrorCalled, 'onError perf handler not called' )
	assert( tmpTable.onCompletedCalled == false, 'onCompleted perf handler should not have been called' )
	
	rxTest:expectSingleValue( WFPerformanceReport.retrieve( true ): map( checkLogsTable ), true )
end

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

function WFPerformanceReportTest.rxTestWrapObservableValueWithFlag( rxTest )

	rxTest:setTimeLimit( 30 )
	rxTest:makeVerbose()
	local src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndCompleted()
	local optTable = {}
	
	-- case #1: when observableValue is set true. We should expect 'observableValue' in 
	-- metric table populated with actual value of observable recorded at that event.
	optTable[ 'observableValue' ] = true
	local o = rxTest:observe( src:call( recordObservable, "rxTestMultipleEventsAndCompleted", optTable ) )
	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectCompleted()
	rxTest:expectSingleValue( WFPerformanceReport.retrieve( true ):map( checkObservableValueInTable ), "observableValue is present" )
	
	-- case #2: when observableValue is set false. There will be no 'observableValue' entry in 
	-- metric table populated during recording.
	--src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndCompleted()
	optTable[ 'observableValue' ] = false
	o = rxTest:observe( src:call( recordObservable, "rxTestMultipleEventsAndCompleted", optTable ) )
	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectCompleted()
	rxTest:expectSingleValue( WFPerformanceReport.retrieve( true ):map( checkObservableValueInTable ), "observableValue is missing" )

	-- case #3: when observableValue is not set or say nil. There will not be 'observableValue' entry in 
	-- metric table populated during redording.
	--src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndCompleted()
	o = rxTest:observe( src:call( recordObservable, "rxTestMultipleEventsAndCompleted" ) )
	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectCompleted()
	rxTest:expectSingleValue( WFPerformanceReport.retrieve( true ):map( checkObservableValueInTable ), "observableValue is missing" )

end

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

WFPerformanceReportTest.rxTestWrapObservableValueWithFlag =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestWrapObservableValueWithFlag )

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

function WFPerformanceReportTest.rxTestWrapObservableValue( rxTest )

	rxTest:setTimeLimit( 30 )
	rxTest:makeVerbose()
	local src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndCompleted()
	
	-- case #1: test that convenience API works like case 1 in rxTestWrapObservableValueWithFlag
	local o = rxTest:observe( 
				WFPerformanceReport.wrapObservableValue( 
					"rxTestWrapObservableValue", src ) )

	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectCompleted()

	rxTest:expectSingleValue( 
			WFPerformanceReport.retrieve( true )
				:map( checkObservableValueInTable ), 
			"observableValue is present" )

end

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

WFPerformanceReportTest.rxTestWrapObservableValue =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestWrapObservableValue )

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

function WFPerformanceReportTest.rxTestWrapObservableFunction( rxTest )

	rxTest:setTimeLimit( 30 )
	rxTest:makeVerbose()

	local nEvents = 3
	
	local fn = function()
		return WFPerformanceReportTestUtils.sendsOnNextAndCompleted( nEvents )
	end
	
	local wrappedFn = WFPerformanceReport.wrapObservableFunction( "rxTestWrapObservableFunction", fn )
	
	local o = rxTest:observe( wrappedFn() )

	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectCompleted()

end

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

WFPerformanceReportTest.rxTestWrapObservableFunction =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestWrapObservableFunction )

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

local function makeSequenceGenerator()

	local i = 0

	return function()

		i = i + 1

		return i

	end 

end

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

local function makeEventStream( interval, n )

	local generateSequenceItem = makeSequenceGenerator()

	return Rx.Observable.timer( 0, interval, WFRxScheduler.sharedScheduler() )
				:map( generateSequenceItem )
				:take( n )

end

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

local function makeGetWrappedInt( s )

	return function( i )

		return string.format( "%s %s", s, i )

	end

end

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

local function wrapRetInTable( f )

	return function( ... )

		return { f( ... ) }

	end

end

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

local function makeCheckNEventsLogged( n, expectedEventValueFunc, rxTest )

	return function( events )

		if type( events ) ~= 'table' then 
		
			rxTest:log( "No events table" )

			return false

		end

		if not next( events ) then
		
			rxTest:log( "Empty events table" )

			return false

		end

		local actualEventsSeen = 0

		for i, doc in ipairs( events ) do

			if doc.key ~= "tryTapObservableOnNext onNext" then

				rxTest:logf( "expected doc.key not seen. Saw '%s'", doc.key )

				return false

			end

			if type( doc.value ) ~= 'table' then

				rxTest:logf( "expected doc.key not seen. Saw %s", doc.key )

				return false

			end

			local actualValue = doc.value[ 1 ]

			if actualValue ~= expectedEventValueFunc( i ) then

				rxTest:logf( "expected transformed doc.value not seen. Saw %s instead of %s",
											actualValue, expectedEventValueFunc( i ) )

				return false

			end
		
			actualEventsSeen = actualEventsSeen + 1

		end

		return actualEventsSeen == n

		end

end

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

function WFPerformanceReportTest.rxTestTapObservableOnNext( rxTest )

	rxTest:setTimeLimit( 30 )

	local n = 5

	local eventStream = makeEventStream( 1 / 5, n )

	local generateCallStr = makeGetWrappedInt( "onNextTransform" )

	local captureAndTransformOnNext = wrapRetInTable( generateCallStr )

	local tappedOnNext = WFPerformanceReport.tapObservableOnNext( 
									"tryTapObservableOnNext", eventStream, 
									captureAndTransformOnNext )
	

	local observedEventStream = rxTest:observe( tappedOnNext )

	for i = 1, n do

        observedEventStream:expectData( i )

	end

	observedEventStream:expectCompleted()

	local checkPerformanceReportEvents = 
			makeCheckNEventsLogged( n, generateCallStr, rxTest )

	local performanceReportEventsStream = 
			WFPerformanceReport.retrieve( true )

	rxTest:expectSingleValue( 
			performanceReportEventsStream
				:map( checkPerformanceReportEvents ),
			true )

end

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

WFPerformanceReportTest.rxTestTapObservableOnNext =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestTapObservableOnNext )

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

function WFPerformanceReportTest.rxTestOmitAlwaysEnableWFPerformanceReport( rxTest )

	-- this test does not pass isUnitTest = true, nor is it wrapped in 
	-- WFPerformanceReport.alwaysEnableWFPerformanceReport
	--
	-- Therefore we expect it to not gather performance metrics

	rxTest:setTimeLimit( 30 )
	rxTest:makeVerbose()
	local src, nEvents = WFPerformanceReportTestUtils.sendsOnNextAndCompleted()
	
	-- case #1: call a wrapper API, but do not wrap this test in 
	-- WFPerformanceReport.alwaysEnableWFPerformanceReport. We shouldn't gather
	-- any data.
	local o = rxTest:observe( 
				WFPerformanceReport.wrapObservableValue( 
					"rxTestAlwaysEnableWFPerformanceReport", src ) )

	for i = 1, nEvents do
        o:expectData( i )
	end

	o:expectCompleted()

	rxTest:expectSingleValue( 
			WFPerformanceReport.retrieve( true )
				:map( checkObservableValueInTable ), 
			'empty log table' )

end

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

local function pollingFunction( str, sequenceGeneratorFunc )

	return { str, sequenceGeneratorFunc() }

end

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

function WFPerformanceReportTest.rxTestRegisterPollingMetric( rxTest )

	rxTest:setTimeLimit( 30 )

	local pollInterval = 0.25

	local minPollingCallCount = 5

	local staticArgument1 = "staticArgument1"

	local pollerSubscription =
		WFPerformanceReport.registerPollingMetric( 
								pollInterval, 
								"rxTestRegisterPollingMetric",
								pollingFunction, 
									staticArgument1,
									makeSequenceGenerator() )

	local waitInterval = 2 * minPollingCallCount * pollInterval

	local waitLongEnoughToPoll = 
			rxTest:observe( 
				Rx.Observable.timer( 0, waitInterval,
										WFRxScheduler.sharedScheduler() )
					:take( 2 ) )

	waitLongEnoughToPoll:expectData( ':ignore:' )

	waitLongEnoughToPoll:expectData( ':ignore:' )

	waitLongEnoughToPoll:expectCompleted()

	WFPerformanceReport.deregisterPollingMetric( pollerSubscription )

	rxTest:expectSingleValue( 
			WFPerformanceReport.retrieve( true )
				:map( 
					function( events )

						if type( events ) ~= 'table' then 
						
							return events 
							
						end

						if not next( events ) then 

							return 'empty log table' 
							
						end

						local totalPollingMetricsSeen = 0

						for i, doc in ipairs( events ) do

							if doc.key ~= "rxTestRegisterPollingMetric" then

								return string.format( "unexpected report data key", doc.key )

							end

							local metricsTable = doc.value

							if type( metricsTable ) ~= 'table' then

								return "metrics was not a table"

							end

							local arg1, arg2 = unpack( metricsTable )

							if arg1 ~= staticArgument1 then

								return string.format( "argument 1 was '%s' but should have been '%s'", 
														arg1, staticArgument1 )

							end

							if arg2 ~= i then

								return string.format( "argument 2 was '%s' but should have been '%s'", 
														arg2, i )

							end

							totalPollingMetricsSeen = totalPollingMetricsSeen + 1

						end

						return totalPollingMetricsSeen >= minPollingCallCount and
									'expected polling values were received' or
									string.format( "Only saw %s polling metrics when we expected at least %s",
														totalPollingMetricsSeen, minPollingCallCount )

					end), 
			'expected polling values were received' )

end

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

WFPerformanceReportTest.rxTestRegisterPollingMetric =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestRegisterPollingMetric )

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

function WFPerformanceReportTest.rxTestRegisterPollingMetricGC( rxTest )

	rxTest:setTimeLimit( 30 )

	 for i, shouldRetain in ipairs( { false, true, true, false } ) do

	 	rxTest:logf( "test run %s with shouldRetain = %s", i, shouldRetain )

		local didCallPollFunction = false

		local function gcPollingFunction()

			didCallPollFunction = true

			return { "ranPoller" }

		end

		local arg1 = {}

		local arg2 = {}

		local weakArgs = KSTableUtils.makeWithMode( "kv" )

		weakArgs.arg1 = arg1

		weakArgs.arg2 = arg2

		local pollInterval = 0.25

		local pollerSubscription =
			WFPerformanceReport.registerPollingMetric( 
									pollInterval, 
									"garbage collected polling metric",
									gcPollingFunction, 
										arg1, arg2 )

		pollerSubscription = shouldRetain and pollerSubscription or nil

		-- release strong references to the args
		arg1 = nil

		arg2 = nil

		-- force garbage collection
		
		collectgarbage()

		collectgarbage()

		collectgarbage()

		collectgarbage()

		local waitInterval = 2 * pollInterval

		local waitLongEnoughToPoll = 
				rxTest:observe( 
					Rx.Observable.timer( waitInterval, waitInterval,
											WFRxScheduler.sharedScheduler() )
						:take( 2 ) )

		waitLongEnoughToPoll:expectData( ':ignore:' )

		waitLongEnoughToPoll:expectData( ':ignore:' )

		waitLongEnoughToPoll:expectCompleted()

		-- arg1 and arg2 should only be alive if they we kept alive by 
		-- retaining the pollerSubscription
		rxTest:expectEqual( weakArgs.arg1 == nil, not shouldRetain )

		rxTest:expectEqual( weakArgs.arg2 == nil, not shouldRetain )

		rxTest:expectEqual( didCallPollFunction, shouldRetain )

		if pollerSubscription then

			WFPerformanceReport.deregisterPollingMetric( pollerSubscription )

			collectgarbage()

			collectgarbage()

			collectgarbage()

			collectgarbage()

			rxTest:expectEqual( weakArgs.arg1 == nil, true )

			rxTest:expectEqual( weakArgs.arg2 == nil, true )

		end

	end

end

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

WFPerformanceReportTest.rxTestRegisterPollingMetricGC =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestRegisterPollingMetricGC )

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

function WFPerformanceReportTest.rxTestWrapFunction( rxTest )

	rxTest:setTimeLimit( 30 )

	local sayWut = makeGetWrappedInt( 'wut ' )

	local wrappedFunction =
		WFPerformanceReport.wrapFunction( "wrappingTest", sayWut )

	local numWrappedCalls = 10

	for i = 1, numWrappedCalls do

		rxTest:expectEqual( wrappedFunction( i ), sayWut( i ) )

	end

	rxTest:expectSingleValue( 
			WFPerformanceReport.retrieve( true )
				:map( 
					function( events )

						if type( events ) ~= 'table' then 
						
							return events 
							
						end

						if not next( events ) then 

							return 'empty log table' 
							
						end

						local totalCalls = 0

						for _, doc in ipairs( events ) do

							if doc.key ~= "wrappingTest" then

								return string.format( "unexpected report data key", doc.key )

							end

							local metricsTable = doc.value

							if type( metricsTable ) ~= 'table' then

								return "metrics was not a table"

							end

							if not metricsTable.timestamp then

								return 'missing timestamp'

							end

							if not metricsTable.timeIntervalSinceAppLaunch then

								return 'missing timeIntervalSinceAppLaunch'

							end

							totalCalls = totalCalls + 1

						end

						return totalCalls == numWrappedCalls and
									'expected wrapped calls were received' or
									string.format( "incorrect number wrapped calls got %s when we expected %s",
														totalCalls, numWrappedCalls )

					end), 
			'expected wrapped calls were received' )

end

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

WFPerformanceReportTest.rxTestWrapFunction =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestWrapFunction )

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

local function abcAnd( ... )

	return 'a', 'b', 'c', ...

end

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

local function reverseArgs( ... )

	local reversed = {}

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

	if nArgs == 0 then

		return

	end

	local i = nArgs

	while i >= 1 do

		reversed[ nArgs - i + 1 ] = select( i, ... )

		i = i - 1

	end

	return unpack( reversed, 1, nArgs )

end

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

function WFPerformanceReportTest.rxTestTapFunctionInput( rxTest )

	rxTest:setTimeLimit( 30 )

	local wrappedAbcAnd = WFPerformanceReport.tapFunctionInput( 
								"tapFunctionInputTest", abcAnd, 
								wrapRetInTable( reverseArgs ) )

	rxTest:expectEqual( { wrappedAbcAnd( 'd', 'e', 'f' ) }, 
							{ 'a', 'b', 'c', 'd', 'e', 'f'  } )

	rxTest:expectSingleValue( 
			WFPerformanceReport.retrieve( true )
				:map( 
					function( events )

						if type( events ) ~= 'table' then 
						
							return 'events is not a table'
							
						end

						local event = events[ 1 ]

						if type( event ) ~= 'table' then

							return 'no events captured'
							
						end

						if event.key ~= "tapFunctionInputTest" then

							return string.format( "unexpected event key", event.key )

						end

						local metricsTable = event.value

						if type( metricsTable ) ~= 'table' then

							return "metrics was not a table"

						end

						if not metricsTable.timestamp then

							return 'missing timestamp'

						end

						if not metricsTable.timeIntervalSinceAppLaunch then

							return 'missing timeIntervalSinceAppLaunch'

						end

						local expectedValue = { 'f', 'e', 'd' }

						local equal = #expectedValue == #metricsTable 

						if equal then

							for i = 1, #expectedValue do

								equal = equal and expectedValue[ i ] == metricsTable[ i ]

							end

						end

						return equal and 
								'got expected tapInputFunc data' or 
								'missing expected tapInputFunc data'


					end), 
			'got expected tapInputFunc data' )

end

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

WFPerformanceReportTest.rxTestTapFunctionInput =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestTapFunctionInput )

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

function WFPerformanceReportTest.rxTestTapFunctionOutput( rxTest )

	rxTest:setTimeLimit( 30 )


	local wrappedAbcAnd = WFPerformanceReport.tapFunctionOutput( 
								"tapFunctionOutputTest", abcAnd, 
								wrapRetInTable( reverseArgs ) )

	rxTest:expectEqual( { wrappedAbcAnd( 'd', 'e', 'f' ) }, 
							{ 'a', 'b', 'c', 'd', 'e', 'f'  } )

	rxTest:expectSingleValue( 
			WFPerformanceReport.retrieve( true )
				:map( 
					function( events )

						if type( events ) ~= 'table' then 
						
							return 'events is not a table'
							
						end

						local event = events[ 1 ]

						if type( event ) ~= 'table' then

							return 'no events captured'
							
						end

						if event.key ~= "tapFunctionOutputTest" then

							return string.format( "unexpected event key", event.key )

						end

						local metricsTable = event.value

						if type( metricsTable ) ~= 'table' then

							return "metrics was not a table"

						end

						if not metricsTable.timestamp then

							return 'missing timestamp'

						end

						if not metricsTable.timeIntervalSinceAppLaunch then

							return 'missing timeIntervalSinceAppLaunch'

						end

						local expectedValue = { 'f', 'e', 'd', 'c', 'b', 'a' }

						local equal = #expectedValue == #metricsTable 

						if equal then

							for i = 1, #expectedValue do

								equal = equal and expectedValue[ i ] == metricsTable[ i ]

							end

						end

						return equal and 
								'got expected tapOutputFunc data' or 
								'missing expected tapOutputFunc data'


					end), 
			'got expected tapOutputFunc data' )

end

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

WFPerformanceReportTest.rxTestTapFunctionOutput =
	WFPerformanceReport.alwaysEnableWFPerformanceReport( 
		WFPerformanceReportTest.rxTestTapFunctionOutput )

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

return WFRxTestCase.wrapSuite( WFPerformanceReportTest )
