diff --git a/src/libs/extensionsystem/optionsparser.cpp b/src/libs/extensionsystem/optionsparser.cpp index 29e790c8d7c..5ad79b1f0a1 100644 --- a/src/libs/extensionsystem/optionsparser.cpp +++ b/src/libs/extensionsystem/optionsparser.cpp @@ -41,6 +41,7 @@ const char *OptionsParser::NO_LOAD_OPTION = "-noload"; const char *OptionsParser::LOAD_OPTION = "-load"; const char *OptionsParser::TEST_OPTION = "-test"; const char *OptionsParser::NOTEST_OPTION = "-notest"; +const char *OptionsParser::SCENARIO_OPTION = "-scenario"; const char *OptionsParser::PROFILE_OPTION = "-profile"; const char *OptionsParser::NO_CRASHCHECK_OPTION = "-no-crashcheck"; @@ -85,6 +86,8 @@ bool OptionsParser::parse() #ifdef WITH_TESTS if (checkForTestOptions()) continue; + if (checkForScenarioOption()) + continue; #endif if (checkForAppOption()) continue; @@ -167,6 +170,28 @@ bool OptionsParser::checkForTestOptions() return false; } +bool OptionsParser::checkForScenarioOption() +{ + if (m_currentArg == QLatin1String(SCENARIO_OPTION)) { + if (nextToken(RequiredToken)) { + if (!m_pmPrivate->m_requestedScenario.isEmpty()) { + if (m_errorString) { + *m_errorString = QCoreApplication::translate("PluginManager", + "Can't request scenario \"%1\" as the scenario \"%1\" was already requested.") + .arg(m_currentArg, m_pmPrivate->m_requestedScenario); + } + m_hasError = true; + } else { + // It's called before we register scenarios, so we don't check if the requested + // scenario was already registered yet. + m_pmPrivate->m_requestedScenario = m_currentArg; + } + } + return true; + } + return false; +} + bool OptionsParser::checkForLoadOption() { if (m_currentArg != QLatin1String(LOAD_OPTION)) diff --git a/src/libs/extensionsystem/optionsparser.h b/src/libs/extensionsystem/optionsparser.h index 80706aecea5..155d5a24788 100644 --- a/src/libs/extensionsystem/optionsparser.h +++ b/src/libs/extensionsystem/optionsparser.h @@ -48,6 +48,7 @@ public: static const char *LOAD_OPTION; static const char *TEST_OPTION; static const char *NOTEST_OPTION; + static const char *SCENARIO_OPTION; static const char *PROFILE_OPTION; static const char *NO_CRASHCHECK_OPTION; @@ -58,6 +59,7 @@ private: bool checkForLoadOption(); bool checkForNoLoadOption(); bool checkForTestOptions(); + bool checkForScenarioOption(); bool checkForAppOption(); bool checkForPluginOption(); bool checkForProfilingOption(); diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index 2efc73e97e2..c8110f00000 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -61,6 +61,7 @@ #ifdef WITH_TESTS #include #include +#include #endif #include @@ -748,6 +749,9 @@ void PluginManager::formatOptions(QTextStream &str, int optionIndentation, int d formatOption(str, QString::fromLatin1(OptionsParser::NOTEST_OPTION), QLatin1String("plugin"), QLatin1String("Exclude all of the plugin's tests from the test run"), optionIndentation, descriptionIndentation); + formatOption(str, QString::fromLatin1(OptionsParser::SCENARIO_OPTION), + QString("scenarioname"), QLatin1String("Run given scenario"), + optionIndentation, descriptionIndentation); #endif } @@ -787,6 +791,96 @@ bool PluginManager::testRunRequested() return !d->testSpecs.empty(); } +#ifdef WITH_TESTS +// Called in plugin initialization, the scenario function will be called later, from main +bool PluginManager::registerScenario(const QString &scenarioId, std::function scenarioStarter) +{ + if (d->m_scenarios.contains(scenarioId)) { + const QString warning = QString("Can't register scenario \"%1\" as the other scenario was " + "already registered with this name.").arg(scenarioId); + qWarning("%s", qPrintable(warning)); + return false; + } + + d->m_scenarios.insert(scenarioId, scenarioStarter); + return true; +} + +// Called from main +bool PluginManager::isScenarioRequested() +{ + return !d->m_requestedScenario.isEmpty(); +} + +// Called from main (may be squashed with the isScenarioRequested: runScenarioIfRequested). +// Returns false if scenario couldn't run (e.g. no Qt version set) +bool PluginManager::runScenario() +{ + if (d->m_isScenarioRunning) { + qWarning("Scenario is already running. Can't run scenario recursively."); + return false; + } + + if (d->m_requestedScenario.isEmpty()) { + qWarning("Can't run any scenario since no scenario was requested."); + return false; + } + + if (!d->m_scenarios.contains(d->m_requestedScenario)) { + const QString warning = QString("Requested scenario \"%1\" was not registered.").arg(d->m_requestedScenario); + qWarning("%s", qPrintable(warning)); + return false; + } + + d->m_isScenarioRunning = true; + // The return value comes now from scenarioStarted() function. It may fail e.g. when + // no Qt version is set. Initializing the scenario may take some time, that's why + // waitForScenarioFullyInitialized() was added. + bool ret = d->m_scenarios[d->m_requestedScenario](); + + QMutexLocker locker(&d->m_scenarioMutex); + d->m_scenarioFullyInitialized = true; + d->m_scenarioWaitCondition.wakeAll(); + + return ret; +} + +// Called from scenario point (and also from runScenario - don't run scenarios recursively). +// This may be called from non-main thread. We assume that m_requestedScenario +// may only be changed from the main thread. +bool PluginManager::isScenarioRunning(const QString &scenarioId) +{ + return d->m_isScenarioRunning && d->m_requestedScenario == scenarioId; +} + +// This may be called from non-main thread. +bool PluginManager::finishScenario() +{ + if (!d->m_isScenarioRunning) + return false; // Can't finish not running scenario + + if (d->m_isScenarioFinished.exchange(true)) + return false; // Finish was already called before. We return false, as we didn't finish it right now. + + QMetaObject::invokeMethod(d, []() { emit m_instance->scenarioFinished(0); }); + return true; // Finished successfully. +} + +// Waits until the running scenario is fully initialized +void PluginManager::waitForScenarioFullyInitialized() +{ + if (QThread::currentThread() == qApp->thread()) { + qWarning("The waitForScenarioFullyInitialized() function can't be called from main thread."); + return; + } + QMutexLocker locker(&d->m_scenarioMutex); + if (d->m_scenarioFullyInitialized) + return; + + d->m_scenarioWaitCondition.wait(&d->m_scenarioMutex); +} +#endif + /*! \internal */ @@ -867,6 +961,14 @@ void PluginManagerPrivate::nextDelayedInitialize() #ifdef WITH_TESTS if (PluginManager::testRunRequested()) startTests(); + else if (PluginManager::isScenarioRequested()) { + if (PluginManager::runScenario()) { + const QString info = QString("Successfully started scenario \"%1\"...").arg(d->m_requestedScenario); + qInfo("%s", qPrintable(info)); + } else { + QMetaObject::invokeMethod(this, []() { emit m_instance->scenarioFinished(1); }); + } + } #endif } else { delayedInitializeTimer->start(); diff --git a/src/libs/extensionsystem/pluginmanager.h b/src/libs/extensionsystem/pluginmanager.h index cb9bc81d317..341a35411ee 100644 --- a/src/libs/extensionsystem/pluginmanager.h +++ b/src/libs/extensionsystem/pluginmanager.h @@ -121,6 +121,18 @@ public: static bool testRunRequested(); +#ifdef WITH_TESTS + static bool registerScenario(const QString &scenarioId, std::function scenarioStarter); + static bool isScenarioRequested(); + static bool runScenario(); + static bool isScenarioRunning(const QString &scenarioId); + // static void triggerScenarioPoint(const QVariant pointData); // ?? called from scenario point + static bool finishScenario(); + static void waitForScenarioFullyInitialized(); + // signals: + // void scenarioPointTriggered(const QVariant pointData); // ?? e.g. in StringTable::GC() -> post a call to quit into main thread and sleep for 5 seconds in the GC thread +#endif + static void profilingReport(const char *what, const PluginSpec *spec = nullptr); static QString platformName(); @@ -139,6 +151,7 @@ signals: void pluginsChanged(); void initializationDone(); void testsFinished(int failedTests); + void scenarioFinished(int exitCode); friend class Internal::PluginManagerPrivate; }; diff --git a/src/libs/extensionsystem/pluginmanager_p.h b/src/libs/extensionsystem/pluginmanager_p.h index 47968fe943a..077fd7bdb13 100644 --- a/src/libs/extensionsystem/pluginmanager_p.h +++ b/src/libs/extensionsystem/pluginmanager_p.h @@ -30,11 +30,13 @@ #include #include +#include #include #include #include #include #include +#include #include @@ -143,6 +145,14 @@ public: bool m_isInitializationDone = false; bool enableCrashCheck = true; + QHash> m_scenarios; + QString m_requestedScenario; + std::atomic_bool m_isScenarioRunning = false; // if it's running, the running one is m_requestedScenario + std::atomic_bool m_isScenarioFinished = false; // if it's running, the running one is m_requestedScenario + bool m_scenarioFullyInitialized = false; + QMutex m_scenarioMutex; + QWaitCondition m_scenarioWaitCondition; + private: PluginManager *q; diff --git a/src/plugins/coreplugin/icore.cpp b/src/plugins/coreplugin/icore.cpp index 52695e3cf65..cfd207faef9 100644 --- a/src/plugins/coreplugin/icore.cpp +++ b/src/plugins/coreplugin/icore.cpp @@ -210,6 +210,10 @@ ICore::ICore(MainWindow *mainwindow) qWarning("Test run was not successful: %d test(s) failed.", failedTests); QCoreApplication::exit(failedTests); }); + connect(PluginManager::instance(), &PluginManager::scenarioFinished, [this] (int exitCode) { + emit coreAboutToClose(); + QCoreApplication::exit(exitCode); + }); } /*!