| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- ///////////////////////////////////////////////////////////////////////////////
- // Copyright (c) Electronic Arts Inc. All rights reserved.
- ///////////////////////////////////////////////////////////////////////////////
- #include <EAStdC/internal/Config.h>
- #include <EAStdCTest/EAStdCTest.h>
- #include <EAStdC/EACallback.h>
- #include <EAStdC/EAStopwatch.h>
- #include <EAStdC/EARandom.h>
- #include <EAStdC/EARandomDistribution.h>
- #include <EATest/EATest.h>
- #include <eathread/eathread.h>
- #include <EAAssert/eaassert.h>
- #ifdef _MSC_VER
- #pragma warning(push, 0)
- #endif
- #include <stdio.h>
- #include <stdlib.h>
- #ifdef _MSC_VER
- #pragma warning(pop)
- #endif
- #if defined(EA_PLATFORM_DESKTOP)
- // We need to test/debug this module in detail on machines before we enable the test
- // formally for them. Given how threads work on other platforms this test or EACallback
- // could fail on other platforms in async mode.
- #define EASTDC_EACALLBACK_TESTS_ENABLED 1
- #else
- #define EASTDC_EACALLBACK_TESTS_ENABLED 0
- #endif
- #if EASTDC_EACALLBACK_TESTS_ENABLED
- ///////////////////////////////////////////////////////////////////////////////
- // CallbackTest
- ///////////////////////////////////////////////////////////////////////////////
- class CallbackTest;
- class CallbackTest : public EA::StdC::CallbackT<CallbackTest>
- {
- public :
- CallbackTest();
- void Reset();
- float GetAccuracyRate() const;
- void CallbackFunction(EA::StdC::Callback* pCallback, uint64_t absoluteValue, uint64_t deltaValue);
- public:
- enum TestParams
- {
- kTestTicks = 100,
- kTestTime = 1000,
- kUserModeEventPulseCount = 20
- };
- uint32_t mIndex; // Which test we are in the array of concurrent tests.
- EA::StdC::Stopwatch mTestStopwatch; // Causes us to stop the callbacks after kTestTime has passed.
- EA::StdC::Stopwatch mStopwatch; // Used to make sure that the deltaValue argument matches the actual time delta since last called.
- uint64_t mNextUnitsMin; // The minimum time we should be next called back.
- uint64_t mNextUnitsMax; // The maximum time we should be next called back.
- uint64_t mLastUnits; // The last time we were called back.
- uint32_t mGoodSampleCount; // Total number of times our callback function was called and it happened within the time period expected.
- uint32_t mBadSampleCount; // Total number of times our callback function was called and it didn't happen within the time period expected.
- uint32_t mTotalSampleCount; // Total number of times our callback function was called. Equals mGoodSampleCount + mBadSampleCount.
- uint32_t mAddRefCount; // Number of times the AddRef message was sent.
- uint32_t mReleaseCount; // Number of times the Release message was sent.
- Mode mMode; // Used so we can tell how to validate the tests.
- };
- CallbackTest::CallbackTest()
- : mIndex(0),
- mTestStopwatch(EA::StdC::Stopwatch::kUnitsNanoseconds),
- mStopwatch(EA::StdC::Stopwatch::kUnitsNanoseconds),
- mNextUnitsMin(0),
- mNextUnitsMax(0),
- mLastUnits(0),
- mGoodSampleCount(0),
- mBadSampleCount(0),
- mTotalSampleCount(0),
- mAddRefCount(0),
- mReleaseCount(0),
- mMode(EA::StdC::Callback::kModeSync)
- {
- SetFunctionInfo(&CallbackTest::CallbackFunction, this, true);
- }
- void CallbackTest::Reset()
- {
- // Leave mIndex as-is.
- mNextUnitsMin = 0;
- mNextUnitsMax = 0;
- mTotalSampleCount = 0;
- mGoodSampleCount = 0;
- mBadSampleCount = 0;
- mLastUnits = 0;
- mAddRefCount = 0;
- mReleaseCount = 0;
- // Leave mMode as-is.
- }
- float CallbackTest::GetAccuracyRate() const
- {
- // Returns a value between 0.0 and 100.0.
- return (float)(100.0 * (mTotalSampleCount ? ((double)mGoodSampleCount / (double)mTotalSampleCount) : 1.0));
- }
- void CallbackTest::CallbackFunction(EA::StdC::Callback* /*pCallback*/, uint64_t absoluteValue, uint64_t deltaValue)
- {
- using namespace EA::StdC;
- if(absoluteValue == Callback::kMessageAddRef)
- mAddRefCount++;
- else if(absoluteValue == Callback::kMessageRelease)
- mReleaseCount++;
- else
- {
- const Callback::Type type = GetType();
- const uint64_t period = GetPeriod();
- const uint64_t precision = GetPrecision();
- if(mLastUnits == 0) // If this is the first time being called...
- mTestStopwatch.Restart();
- else
- {
- ++mTotalSampleCount;
- if((absoluteValue >= mNextUnitsMin) && (absoluteValue <= mNextUnitsMax))
- {
- if(type == Callback::kTypeTime)
- {
- const uint64_t elapsedTime = mStopwatch.GetElapsedTime();
- const uint64_t difference = (deltaValue > elapsedTime) ? (deltaValue - elapsedTime) : (elapsedTime - deltaValue);
- const double diffPercent = ((double)(int64_t)difference / (double)(int64_t)elapsedTime) * 100.0; // Cast to int64_t because some compilers/platforms can't handle int64_t to double conversions.
- if((mMode == Callback::kModeAsync) && (diffPercent < 10)) // In threaded mode there's more room for failure.
- ++mGoodSampleCount;
- else if((mMode == Callback::kModeSync) && (diffPercent < 3))
- ++mGoodSampleCount;
- else
- ++mBadSampleCount;
- }
- else // Else kTypeTick or kTypeUserEvent.
- ++mGoodSampleCount;
- }
- else
- {
- // It turns out that with kModeAsync (threaded), we can get a lot of these failures with kTypeUserEvent.
- // This is because the main thread is updating the user event counter (because it has to) while this
- // callback thread is running independently. It's possible for the main thread to bump up the user event
- // counter while this callback thread is in the process of issuing the next call. So timeNS could
- ++mBadSampleCount;
- }
- }
- mStopwatch.Restart();
- // Set the next expectation.
- mLastUnits = absoluteValue;
- mNextUnitsMin = ((absoluteValue + period) > precision) ? (absoluteValue + period - precision) : 0; // Extra logic here to make sure mNextUnitsMin doesn't go negative and wrap around to a very large uint64_t number.
-
- // How to decide the next max expected value is not simple because in async mode we have
- // two threads, one of which is updating the user event count and the other which is
- // servicing callbacks. The former could increase the event count by theoretically any
- // number while the servicing thread is in the middle of a single callback call.
- // We can fix this by having the main thread wait until the servicing thread is not
- // busy, or we can put some cap on the expected value. Currently we do the latter,
- // because it's simpler, but the former is probably better because it lets us test more precisely.
- if(type == Callback::kTypeUserEvent)
- mNextUnitsMax = absoluteValue + period + precision + (kUserModeEventPulseCount * 3);
- else
- mNextUnitsMax = absoluteValue + period + precision + (int)(1 + (double)period * 0.05f); // Add a little extra slop to account for CPU stalls and what-not.
-
- // See if the test is complete
- switch (type)
- {
- case Callback::kTypeTick:
- if(mTotalSampleCount >= kTestTicks)
- Stop();
- break;
- case Callback::kTypeUserEvent:
- if(mTotalSampleCount >= kTestTicks)
- Stop();
- break;
- case Callback::kTypeTime:
- {
- const uint64_t elapsedTime = mTestStopwatch.GetElapsedTime();
- if(elapsedTime > kTestTime)
- Stop();
- break;
- }
- }
- }
- }
- ///////////////////////////////////////////////////////////////////////////////
- // TestCallback
- ///////////////////////////////////////////////////////////////////////////////
- struct TestControlInfo
- {
- uint32_t mPeriodStart;
- uint32_t mPeriodEnd;
- uint32_t mPeriodStep;
- uint32_t mPrecisionStart;
- uint32_t mPrecisionEnd;
- uint32_t mPrecisionStep;
- EA::StdC::Callback::Type mType;
- uint32_t mTestTimeMs;
- };
- #if defined(EA_PLATFORM_DESKTOP)
- const TestControlInfo tci[] =
- {
- // Period Precision Type Test max time ms
- // -------------------------------- ------------------------------ ---------------------------------- ----------------
- { 1, 51, 50, 0, 1, 1, EA::StdC::Callback::kTypeTick, 10000 },
- { 1, 51, 50, 0, 1, 1, EA::StdC::Callback::kTypeUserEvent, 10000 },
- { 20000000, 50000000, 30000000, 15000000, 30000000, 15000000, EA::StdC::Callback::kTypeTime, 10000 }, // These are big numbers because time is in nanoseconds.
- { 100000000, 200000000, 100000000, 50000000, 100000000, 50000000, EA::StdC::Callback::kTypeTime, 10000 },
- { 1000000000, 2000000000, 1000000000, 200000000, 400000000, 200000000, EA::StdC::Callback::kTypeTime, 10000 }
- };
- #else
- const TestControlInfo tci[] =
- {
- // Period Precision Type Test max time ms
- // -------------------------------- ------------------------------ ---------------------------------- ----------------
- { 1, 5, 4, 1, 1, 1, EA::StdC::Callback::kTypeTick, 60000 },
- { 1, 5, 4, 1, 1, 1, EA::StdC::Callback::kTypeUserEvent, 60000 },
- { 50000000, 50000000, 1, 30000000, 30000000, 1, EA::StdC::Callback::kTypeTime, 60000 }, // These are big numbers because time is in nanoseconds.
- { 500000000, 500000000, 1, 200000000, 200000000, 1, EA::StdC::Callback::kTypeTime, 60000 }
- };
- #endif
- #endif // EASTDC_EACALLBACK_TESTS_ENABLED
- int TestCallback()
- {
- using namespace EA::StdC;
- int nErrorCount(0);
- #if EASTDC_EACALLBACK_TESTS_ENABLED
- EA::StdC::RandomFast random;
- EA::StdC::CallbackManager callbackManager;
- const uint32_t kCallbackCount = (uint32_t)(100 * EA::UnitTest::GetSystemSpeed(EA::UnitTest::kSpeedTypeCPU)); // Determines the number of callbacks in the callback tests.
- CallbackTest* pCallbackTestArray = new CallbackTest[kCallbackCount];
- EA::StdC::SetCallbackManager(&callbackManager);
- EA::UnitTest::ReportVerbosity(1, "Callback test using %u callbacks. The test shows how well (on average)\n"
- "the callback system is able to satisfy the demands of each callback object.\n", (unsigned)kCallbackCount);
- // For both kModeAsync kModeSync...
- for(int a = 0; a < 2; a++)
- {
- #if EASTDC_THREADING_SUPPORTED
- const Callback::Mode mode = ((a % 2) ? Callback::kModeAsync : Callback::kModeSync);
- #else
- const Callback::Mode mode = Callback::kModeSync;
- #endif
- callbackManager.Shutdown();
- switch(mode)
- {
- case Callback::kModeAsync:
- {
- callbackManager.Init(true, true); // Run in asynchronous (threaded) mode.
- break;
- }
- case Callback::kModeSync:
- default:
- {
- callbackManager.Init(false, false); // Run in synchronous (polled, non-threaded) mode.
- break;
- }
- }
-
- // For each type of TestControlInfo above...
- for(size_t cn = 0, n = EAArrayCount(tci); cn < n; ++cn)
- {
- const TestControlInfo& ci = tci[cn];
- for(uint32_t t = 0; t < kCallbackCount; ++t)
- {
- CallbackTest& callbackTest = pCallbackTestArray[t];
- callbackTest.mIndex = t;
- callbackTest.mMode = mode;
- callbackTest.SetType(ci.mType);
- }
- // For an array of periods between begin and end period...
- for(uint32_t period = ci.mPeriodStart; period <= ci.mPeriodEnd; period += ci.mPeriodStep)
- {
- // For an array of precisions between begin and end precision...
- for(uint32_t precision = ci.mPrecisionStart; precision <= ci.mPrecisionEnd; precision += ci.mPrecisionStep)
- {
- // print to avoid test timeout
- EA::UnitTest::ReportVerbosity(0, "%s", ".");
- EA::UnitTest::ReportVerbosity(1, "Callback test: mode = %s, type = %s, period = %i, precision = %i\n",
- (mode == Callback::kModeSync) ? "Sync" : "Async",
- (ci.mType == Callback::kTypeTick) ? "Tick" : ((ci.mType == Callback::kTypeTime) ? "Time" : "UserEvent"),
- period,
- precision);
- for(uint32_t t = 0; t < kCallbackCount; ++t)
- {
- CallbackTest& callbackTest = pCallbackTestArray[t];
- callbackTest.SetPeriod(period);
- callbackTest.SetPrecision(precision);
- callbackTest.Start(NULL, false);
- }
- // Run the test for N seconds, during which our callback function will be called
- // repeatedly. After the callback function has been called for an amount
- // of kTestTicks or kTestTime, the callback function calls CallbackTimer::Stop.
- bool bCallbacksAreStillRunning = true;
- bool bShouldContinue = true;
- EA::Thread::ThreadTime endTime = EA::Thread::GetThreadTime() + ci.mTestTimeMs;
- uint64_t loopCount; // This is just to help debugging.
- for(loopCount = 0; bShouldContinue && bCallbacksAreStillRunning && (loopCount < UINT64_C(0x0fffffffffffffff)); ++loopCount)
- {
- bCallbacksAreStillRunning = false;
- // Randomly trigger a user event.
- if(EA::StdC::RandomBool(random))
- {
- for(int e = 0; e < CallbackTest::kUserModeEventPulseCount; e++) // Trigger multiple events at once.
- callbackManager.OnUserEvent();
- }
- // If we are in synchronous (i.e. polled) mode, then we need to manually call Update.
- // We don't need to call Update in synchronous (threaded) mode, but it's supported so
- // we test calling Update occasionally to make sure it works.
- switch(mode)
- {
- case Callback::kModeSync:
- {
- callbackManager.Update();
- break;
- }
- case Callback::kModeAsync:
- default:
- {
- if(random.RandomUint32Uniform(100) == 0)
- {
- callbackManager.Update();
- }
- break;
- }
- }
- // Sleep for a little bit.
- EA::Thread::ThreadTime sleepTime = EA::Thread::kTimeoutImmediate;
- #if EASTDC_THREADING_SUPPORTED
- // Since we are in a seperate thread from the callback manager and since we are updating On UserEvent
- // ourselves here, it might be useful to sleep here to handle the case that our OnUserEvent calls get
- // ahead of what the callback manager can keep up with and "CallbackTest low accuracy rate" reports
- // can result, though these reports wouldn't be indicative of bugs or failures of EACallback.
- if((mode == Callback::kModeAsync) && (ci.mType != EA::StdC::Callback::kTypeTime))
- sleepTime = EA::Thread::ThreadTime(10);
- #endif
- EA::Thread::ThreadSleep(sleepTime);
- // See if the alotted time has passed.
- EA::Thread::ThreadTime currentTime = EA::Thread::GetThreadTime();
- bShouldContinue = (currentTime < endTime);
- // See if the callbacks have all been called the number of times we expected and are thus done now.
- for(uint32_t t = 0; (t < kCallbackCount) && !bCallbacksAreStillRunning; ++t)
- {
- const CallbackTest& callbackTest = pCallbackTestArray[t];
- if(callbackTest.IsRunning())
- {
- bCallbacksAreStillRunning = true;
- if(!bShouldContinue) // If the time is up, yet this callback is still running...
- {
- // The callback function didn't get called the expected number of times before our loop time above expired.
- //++nErrorCount; Hard to enable this because it's easy for this test to have some failures. Probably what we should do is count failures and expect some success rate.
- EA::UnitTest::ReportVerbosity(1, "CallbackTest timeout. The callback didn't fire the expected number of\n"
- "times during our test period. Expected: %u, Actual: %u\n",
- (callbackTest.GetType() == Callback::kTypeTime) ? (unsigned)CallbackTest::kTestTicks : (unsigned)CallbackTest::kTestTime, (unsigned)callbackTest.mTotalSampleCount);
- }
- }
- }
- }
- uint32_t successSum = 0;
- for(uint32_t t = 0; t < kCallbackCount; ++t)
- {
- CallbackTest& callbackTest = pCallbackTestArray[t];
- successSum += (uint32_t)callbackTest.GetAccuracyRate(); // GetAccuracyRate returns a value in the range of [0, 100]
- callbackTest.Reset();
- }
- const uint32_t successRate = (successSum / kCallbackCount); // successRate is a value in the range of [0, 100].
- if(successRate < 90) // If less than 90% of the callbacks occurred within the expected time...
- {
- // ++nErrorCount; Hard to enable this because it's easy for this test to have some failures. Probably what we should do is count failures and expect some success rate.
- EA::UnitTest::ReportVerbosity(1, "CallbackTest low accuracy rate of %i%%.\n", successRate);
- }
- }
- }
- }
- }
- delete[] pCallbackTestArray;
- #endif // EA_PLATFORM_DESKTOP
- // to avoid test timeout
- EA::EAMain::ReportVerbosity(0, "%s", "\n");
- return nErrorCount;
- }
|