#include "controller.h" #include <functional> #include <chrono> #include "BaseTest.h" #include "tests.h" USING_NS_CC; #define TEST_TIME_OUT 25 #define CREATE_TIME_OUT 25 #define LOG_INDENTATION " " #define LOG_TAG "[TestController]" class RootTests : public TestList { public: RootTests() { addTest("SpritePolygon", [](){return new (std::nothrow) SpritePolygonTest(); }); addTest("ActionManager", [](){return new (std::nothrow) ActionManagerTests(); }); addTest("Actions - Basic", [](){ return new (std::nothrow) ActionsTests(); }); addTest("Actions - Ease", [](){return new (std::nothrow) ActionsEaseTests(); }); addTest("Actions - Progress", [](){return new (std::nothrow) ActionsProgressTests(); }); addTest("Allocator - Basic", [](){return new (std::nothrow) AllocatorTests(); }); addTest("Audio - CocosDenshion", []() { return new (std::nothrow) CocosDenshionTests(); }); #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) addTest("Audio - NewAudioEngine", []() { return new (std::nothrow) AudioEngineTests(); }); #endif #if CC_ENABLE_BOX2D_INTEGRATION addTest("Box2d - Basic", []() { return new (std::nothrow) Box2DTests(); }); addTest("Box2d - TestBed", []() { return new (std::nothrow) Box2dTestBedSuite(); }); #endif addTest("Bugs", []() { return new BugsTests(); }); addTest("Chipmunk", []() { return new ChipmunkTests(); }); addTest("Click and Move", [](){return new ClickAndMoveTest(); }); addTest("Configuration", []() { return new ConfigurationTests(); }); addTest("Console", []() { return new ConsoleTests(); }); addTest("Curl", []() { return new CurlTests(); }); addTest("Current Language", []() { return new CurrentLanguageTests(); }); addTest("CocosStudio3D Test", []() { return new CocosStudio3DTests(); }); addTest("EventDispatcher", []() { return new EventDispatcherTests(); }); addTest("Effects - Advanced", []() { return new EffectAdvanceTests(); }); addTest("Effects - Basic", [](){return new EffectTests(); }); addTest("Extensions", []() { return new ExtensionsTests(); }); addTest("FileUtils", []() { return new FileUtilsTests(); }); addTest("Fonts", []() { return new FontTests(); }); addTest("Interval", [](){return new IntervalTests(); }); addTest("Node: BillBoard Test", [](){ return new BillBoardTests(); }); addTest("Node: Camera 3D Test", [](){ return new Camera3DTests(); }); addTest("Node: Clipping", []() { return new ClippingNodeTests(); }); addTest("Node: Draw", [](){return new DrawPrimitivesTests(); }); addTest("Node: Label - New API", [](){return new NewLabelTests(); }); addTest("Node: Label - Old API", [](){return new LabelTests(); }); addTest("Node: Layer", [](){return new LayerTests(); }); addTest("Node: Light", [](){return new LightTests(); }); addTest("Node: Menu", [](){return new MenuTests(); }); addTest("Node: MotionStreak", [](){return new MotionStreakTests(); }); addTest("Node: Node", [](){return new CocosNodeTests(); }); addTest("Node: Parallax", [](){return new ParallaxTests(); }); addTest("Node: Particles", [](){return new ParticleTests(); }); addTest("Node: Particle3D (PU)", [](){return new Particle3DTests(); }); addTest("Node: Physics", []() { return new PhysicsTests(); }); addTest("Node: RenderTexture", [](){return new RenderTextureTests(); }); addTest("Node: Scene", [](){return new SceneTests(); }); addTest("Node: Spine", [](){return new SpineTests(); }); addTest("Node: Sprite", [](){return new SpriteTests(); }); addTest("Node: Sprite3D", [](){ return new Sprite3DTests(); }); addTest("Node: Terrain", [](){ return new TerrainTests(); }); addTest("Node: TileMap", [](){return new TileMapTests(); }); addTest("Node: FastTileMap", [](){return new FastTileMapTests(); }); addTest("Node: Text Input", [](){return new TextInputTests(); }); addTest("Node: UI", [](){ return new UITests(); }); addTest("Mouse", []() { return new MouseTests(); }); addTest("MultiTouch", []() { return new MutiTouchTests(); }); //addTest("Performance tests", []() { return new PerformanceTests(); }); addTest("Renderer", []() { return new NewRendererTests(); }); addTest("ReleasePool", [](){ return new ReleasePoolTests(); }); addTest("Rotate World", [](){return new RotateWorldTests(); }); addTest("Scheduler", [](){return new SchedulerTests(); });//!!!!!! addTest("Shader - Basic", []() { return new ShaderTests(); }); addTest("Shader - Sprite", []() { return new Shader2Tests(); }); addTest("Texture2D", [](){return new Texture2DTests(); }); addTest("TextureCache", []() { return new TextureCacheTests(); }); addTest("TexturePacker Encryption", []() { return new TextureAtlasEncryptionTests(); }); addTest("Touches", [](){return new TouchesTests(); }); addTest("Transitions", [](){return new TransitionsTests(); }); addTest("Unit Test", []() { return new UnitTests(); }); addTest("URL Open Test", []() { return new OpenURLTests(); }); addTest("UserDefault", []() { return new UserDefaultTests(); }); addTest("Zwoptex", []() { return new ZwoptexTests(); }); } }; TestController::TestController() : _stopAutoTest(true) , _isRunInBackground(false) , _testSuite(nullptr) { _rootTestList = new (std::nothrow) RootTests; _rootTestList->runThisTest(); _director = Director::getInstance(); _touchListener = EventListenerTouchOneByOne::create(); _touchListener->onTouchBegan = CC_CALLBACK_2(TestController::blockTouchBegan, this); _touchListener->setSwallowTouches(true); _director->getEventDispatcher()->addEventListenerWithFixedPriority(_touchListener, -200); } TestController::~TestController() { _director->getEventDispatcher()->removeEventListener(_touchListener); _rootTestList->release(); _rootTestList = nullptr; } void TestController::startAutoTest() { if (!_autoTestThread.joinable()) { _stopAutoTest = false; _logIndentation = ""; _autoTestThread = std::thread(&TestController::traverseTestList, this, _rootTestList); _autoTestThread.detach(); } } void TestController::stopAutoTest() { _stopAutoTest = true; if (_autoTestThread.joinable()) { _sleepCondition.notify_all(); _autoTestThread.join(); } } void TestController::traverseTestList(TestList* testList) { if (testList == _rootTestList) { _sleepUniqueLock = std::unique_lock<std::mutex>(_sleepMutex); _sleepCondition.wait_for(_sleepUniqueLock, std::chrono::milliseconds(500)); //disable touch } else { _logIndentation += LOG_INDENTATION; _sleepCondition.wait_for(_sleepUniqueLock, std::chrono::milliseconds(500)); } logEx("%s%sBegin traverse TestList:%s", LOG_TAG, _logIndentation.c_str(), testList->getTestName().c_str()); auto scheduler = _director->getScheduler(); int testIndex = 0; for (auto& callback : testList->_testCallbacks) { if (_stopAutoTest) break; while (_isRunInBackground) { logEx("_director is paused"); _sleepCondition.wait_for(_sleepUniqueLock, std::chrono::milliseconds(500)); } if (callback) { auto test = callback(); test->setTestParent(testList); test->setTestName(testList->_childTestNames[testIndex++]); if (test->isTestList()) { scheduler->performFunctionInCocosThread([&](){ test->runThisTest(); }); traverseTestList((TestList*)test); } else { traverseTestSuite((TestSuite*)test); } } } if (testList == _rootTestList) { _sleepUniqueLock.release(); _stopAutoTest = true; } else { if (!_stopAutoTest) { //Backs up one level and release TestList object. scheduler->performFunctionInCocosThread([&](){ testList->_parentTest->runThisTest(); }); _sleepCondition.wait_for(_sleepUniqueLock, std::chrono::milliseconds(500)); testList->release(); } _logIndentation.erase(_logIndentation.rfind(LOG_INDENTATION)); } } void TestController::traverseTestSuite(TestSuite* testSuite) { auto scheduler = _director->getScheduler(); int testIndex = 0; float testCaseDuration = 0.0f; _logIndentation += LOG_INDENTATION; logEx("%s%sBegin traverse TestSuite:%s", LOG_TAG, _logIndentation.c_str(), testSuite->getTestName().c_str()); _logIndentation += LOG_INDENTATION; auto logIndentation = _logIndentation; for (auto& callback : testSuite->_testCallbacks) { auto testName = testSuite->_childTestNames[testIndex++]; Scene* testScene = nullptr; TestCase* testCase = nullptr; TransitionScene* transitionScene = nullptr; if (_stopAutoTest) break; while (_isRunInBackground) { logEx("_director is paused"); _sleepCondition.wait_for(_sleepUniqueLock, std::chrono::milliseconds(500)); } //Run test case in the cocos[GL] thread. scheduler->performFunctionInCocosThread([&, logIndentation, testName](){ if (_stopAutoTest) return; logEx("%s%sRun test:%s.", LOG_TAG, logIndentation.c_str(), testName.c_str()); auto scene = callback(); if (_stopAutoTest) return; if (scene) { transitionScene = dynamic_cast<TransitionScene*>(scene); if (transitionScene) { testCase = (TestCase*)transitionScene->getInScene(); testCaseDuration = transitionScene->getDuration() + 0.5f; } else { testCase = (TestCase*)scene; testCaseDuration = testCase->getDuration(); } testCase->setTestSuite(testSuite); testCase->setTestCaseName(testName); _director->replaceScene(scene); testScene = scene; } }); if (_stopAutoTest) break; //Wait for the test case be created. float waitTime = 0.0f; while (!testScene && !_stopAutoTest) { _sleepCondition.wait_for(_sleepUniqueLock, std::chrono::milliseconds(50)); if (!_isRunInBackground) { waitTime += 0.05f; } if (waitTime > CREATE_TIME_OUT) { logEx("%sCreate test %s time out", LOG_TAG, testName.c_str()); _stopAutoTest = true; break; } } if (_stopAutoTest) break; //Wait for test completed. _sleepCondition.wait_for(_sleepUniqueLock, std::chrono::milliseconds(int(1000 * testCaseDuration))); if (transitionScene == nullptr) { waitTime = 0.0f; while (!_stopAutoTest && testCase->getRunTime() < testCaseDuration) { _sleepCondition.wait_for(_sleepUniqueLock, std::chrono::milliseconds(50)); if (!_isRunInBackground) { waitTime += 0.05f; } if (waitTime > TEST_TIME_OUT) { logEx("%sRun test %s time out", LOG_TAG, testName.c_str()); _stopAutoTest = true; break; } } if (!_stopAutoTest) { //Check the result of test. checkTest(testCase); } } } if (!_stopAutoTest) { //Backs up one level and release TestSuite object. auto parentTest = testSuite->_parentTest; scheduler->performFunctionInCocosThread([&](){ parentTest->runThisTest(); }); _sleepCondition.wait_for(_sleepUniqueLock, std::chrono::milliseconds(1000)); testSuite->release(); } _logIndentation.erase(_logIndentation.rfind(LOG_INDENTATION)); _logIndentation.erase(_logIndentation.rfind(LOG_INDENTATION)); } bool TestController::checkTest(TestCase* testCase) { if (testCase) { switch (testCase->getTestType()) { case TestCase::Type::UNIT: { if (testCase && testCase->getExpectedOutput() != testCase->getActualOutput()) { logEx("%s %s test fail", LOG_TAG, testCase->getTestCaseName().c_str()); } else { logEx("%s %s test pass", LOG_TAG, testCase->getTestCaseName().c_str()); } break; } case TestCase::Type::ROBUSTNESS: { break; } case TestCase::Type::MANUAL: { break; } default: break; } } return true; } void TestController::handleCrash() { logEx("%sCatch an crash event", LOG_TAG); if (!_stopAutoTest) { stopAutoTest(); } #if CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_LINUX exit(1); #endif } void TestController::onEnterBackground() { _isRunInBackground = true; } void TestController::onEnterForeground() { _isRunInBackground = false; } void TestController::logEx(const char * format, ...) { char buff[1024]; va_list args; va_start(args, format); vsnprintf(buff, 1020, format, args); strcat(buff, "\n"); #if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID __android_log_print(ANDROID_LOG_DEBUG, "cocos2d-x debug info", "%s", buff); #elif CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_WP8 || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT WCHAR wszBuf[1024] = { 0 }; MultiByteToWideChar(CP_UTF8, 0, buff, -1, wszBuf, sizeof(wszBuf)); OutputDebugStringW(wszBuf); #else // Linux, Mac, iOS, etc fprintf(stdout, "%s", buff); fflush(stdout); #endif va_end(args); } static TestController* s_testController = nullptr; static void initCrashCatch(); TestController* TestController::getInstance() { if (s_testController == nullptr) { s_testController = new (std::nothrow) TestController; initCrashCatch(); } return s_testController; } void TestController::destroyInstance() { if (s_testController) { s_testController->stopAutoTest(); delete s_testController; s_testController = nullptr; } } bool TestController::blockTouchBegan(Touch* touch, Event* event) { return !_stopAutoTest; } //================================================================================================== #if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 #include <windows.h> static long __stdcall windowExceptionFilter(_EXCEPTION_POINTERS* excp) { if (s_testController) { s_testController->handleCrash(); } return EXCEPTION_EXECUTE_HANDLER; } static void initCrashCatch() { SetUnhandledExceptionFilter(windowExceptionFilter); } #elif CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID #if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID static int s_fatal_signals[] = { SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV, SIGSTKFLT, SIGPIPE, }; #else static int s_fatal_signals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP, SIGTERM, SIGKILL, }; #endif static void signalHandler(int sig) { if (s_testController) { s_testController->handleCrash(); } } static void initCrashCatch() { for (auto sig : s_fatal_signals) { signal(sig, signalHandler); } } #else static void initCrashCatch() { } #endif