From db0a64288001669937e775d090021c0945e0ff3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 2 Jan 2026 11:07:24 +0100 Subject: [PATCH] QTTesting Took 1 hour 28 minutes Took 4 seconds Took 28 minutes Took 8 minutes --- .gitmodules | 3 + CMakeLists.txt | 6 + cockatrice/CMakeLists.txt | 23 ++-- cockatrice/src/interface/window_main.cpp | 139 +++++++++++++++++++++++ cockatrice/src/interface/window_main.h | 3 + external/qttesting | 1 + 6 files changed, 166 insertions(+), 9 deletions(-) create mode 160000 external/qttesting diff --git a/.gitmodules b/.gitmodules index a0a57f3d7..96dfb8c2c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "vcpkg"] path = vcpkg url = https://github.com/microsoft/vcpkg.git +[submodule "external/qttesting"] + path = external/qttesting + url = https://github.com/Kitware/QtTesting.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 574ccecb3..f207dad85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ option(WITH_SERVER "build servatrice" OFF) option(WITH_CLIENT "build cockatrice" ON) # Compile oracle option(WITH_ORACLE "build oracle" ON) +# Compile gui-tests +option(WITH_GUI_TEST "Enable QtTesting-based GUI tests" ON) # Compile tests option(TEST "build tests" OFF) # Use vcpkg regardless of OS @@ -354,6 +356,10 @@ if(WITH_ORACLE) set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) endif() +if(WITH_GUI_TEST) + add_subdirectory(external/qttesting) +endif() + if(TEST) include(CTest) add_subdirectory(tests) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 85e1cd00f..68300081a 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -415,15 +415,15 @@ endif() if(Qt5_FOUND) target_link_libraries( cockatrice - libcockatrice_card - libcockatrice_deck_list - libcockatrice_filters - libcockatrice_utility - libcockatrice_network - libcockatrice_models - libcockatrice_rng - libcockatrice_settings - ${COCKATRICE_QT_MODULES} + PUBLIC libcockatrice_card + libcockatrice_deck_list + libcockatrice_filters + libcockatrice_utility + libcockatrice_network + libcockatrice_models + libcockatrice_rng + libcockatrice_settings + ${COCKATRICE_QT_MODULES} ) else() target_link_libraries( @@ -440,6 +440,11 @@ else() ) endif() +if(WITH_GUI_TEST) + target_link_libraries(cockatrice PRIVATE QtTesting) + target_compile_definitions(cockatrice PRIVATE WITH_GUI_TEST) +endif() + if(UNIX) if(APPLE) set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME}") diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 2e135d170..d133bef57 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -38,6 +38,10 @@ #include "../interface/widgets/tabs/tab_supervisor.h" #include "../main.h" #include "logger.h" +#include "pqEventObserver.h" +#include "pqEventSource.h" +#include "pqEventTypes.h" +#include "pqTestUtility.h" #include "version_string.h" #include "widgets/utility/get_text_with_max.h" @@ -833,6 +837,137 @@ void MainWindow::createMenus() helpMenu->addAction(aOpenSettingsFolder); } +class XMLEventObserver : public pqEventObserver +{ + QXmlStreamWriter *XMLStream; + QString XMLString; + +public: + XMLEventObserver(QObject *p) : pqEventObserver(p) + { + this->XMLStream = NULL; + } + + ~XMLEventObserver() override + { + delete this->XMLStream; + } + +protected: + void setStream(QTextStream *stream) override + { + if (this->XMLStream) { + this->XMLStream->writeEndElement(); + this->XMLStream->writeEndDocument(); + delete this->XMLStream; + this->XMLStream = NULL; + } + if (this->Stream) { + *this->Stream << this->XMLString; + } + this->XMLString = QString(); + pqEventObserver::setStream(stream); + if (this->Stream) { + this->XMLStream = new QXmlStreamWriter(&this->XMLString); + this->XMLStream->setAutoFormatting(true); + this->XMLStream->writeStartDocument(); + this->XMLStream->writeStartElement("events"); + } + } + + void onRecordEvent(const QString &widget, + const QString &command, + const QString &arguments, + const int &eventType) override + { + if (this->XMLStream) { + this->XMLStream->writeStartElement("event"); + this->XMLStream->writeAttribute("widget", widget); + if (eventType == pqEventTypes::ACTION_EVENT) { + this->XMLStream->writeAttribute("command", command); + } else // if(eventType == pqEventTypes::CHECK_EVENT) + { + this->XMLStream->writeAttribute("property", command); + } + this->XMLStream->writeAttribute("arguments", arguments); + this->XMLStream->writeEndElement(); + } + } +}; + +class XMLEventSource : public pqEventSource +{ + typedef pqEventSource Superclass; + QXmlStreamReader *XMLStream; + +public: + XMLEventSource(QObject *p) : Superclass(p) + { + this->XMLStream = NULL; + } + ~XMLEventSource() override + { + delete this->XMLStream; + } + +protected: + void setContent(const QString &xmlfilename) override + { + delete this->XMLStream; + this->XMLStream = NULL; + + QFile xml(xmlfilename); + if (!xml.open(QIODevice::ReadOnly)) { + qDebug() << "Failed to load " << xmlfilename; + return; + } + QByteArray data = xml.readAll(); + this->XMLStream = new QXmlStreamReader(data); + /* This checked for valid event objects, but also caused the first event + * to get dropped. Commenting this out in the example. If you wish to report + * empty XML test files a flag indicating whether valid events were found is + * probably the best way to go. + while (!this->XMLStream->atEnd()) + { + QXmlStreamReader::TokenType token = this->XMLStream->readNext(); + if (token == QXmlStreamReader::StartElement) + { + if (this->XMLStream->name() == "event") + { + break; + } + } + } */ + if (this->XMLStream->atEnd()) { + qDebug() << "Invalid xml"; + } + return; + } + + int getNextEvent(QString &widget, QString &command, QString &arguments, int &eventType) override + { + if (this->XMLStream->atEnd()) { + return DONE; + } + while (!this->XMLStream->atEnd()) { + QXmlStreamReader::TokenType token = this->XMLStream->readNext(); + if (token == QXmlStreamReader::StartElement) { + if (this->XMLStream->name() == "event") { + break; + } + } + } + if (this->XMLStream->atEnd()) { + return DONE; + } + eventType = pqEventTypes::ACTION_EVENT; + widget = this->XMLStream->attributes().value("widget").toString(); + command = this->XMLStream->attributes().value("command").toString(); + arguments = this->XMLStream->attributes().value("arguments").toString(); + return SUCCESS; + } +}; + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), localServer(nullptr), bHasActivated(false), askedForDbUpdater(false), cardUpdateProcess(nullptr), logviewDialog(nullptr) @@ -908,6 +1043,10 @@ MainWindow::MainWindow(QWidget *parent) // run startup check async QTimer::singleShot(0, this, &MainWindow::startupConfigCheck); + + this->TestUtility = new pqTestUtility(this); + this->TestUtility->addEventObserver("xml", new XMLEventObserver(this)); + this->TestUtility->addEventSource("xml", new XMLEventSource(this)); } void MainWindow::startupConfigCheck() diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 75b0f5062..4bb4e2807 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -25,6 +25,8 @@ #ifndef WINDOW_H #define WINDOW_H +#include "pqTestUtility.h" + #include #include #include @@ -161,6 +163,7 @@ private: GameReplay *replay; DlgTipOfTheDay *tip; QUrl connectTo; + // pqTestUtility *TestUtility; public: explicit MainWindow(QWidget *parent = nullptr); diff --git a/external/qttesting b/external/qttesting new file mode 160000 index 000000000..c11a762df --- /dev/null +++ b/external/qttesting @@ -0,0 +1 @@ +Subproject commit c11a762df71d9f44698b93a0aab5dceb59c90e63