Files
cmake/Tests/CMakeGUI/CMakeGUITest.cxx
T
Daksh Mamodiya fdc2c2b9c7 cmake-gui: settle preset application before Configure/Generate
Selecting a configure preset applies on the worker thread, but Configure
and Generate stayed enabled and could snapshot the stale cache model
first. Tokenize preset application, disable the dependent actions until
the completion lands, and accept only the latest request.

Fixes: #28085
2026-09-11 23:24:10 +05:30

660 lines
22 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "CMakeGUITest.h"
#include <QApplication>
#include <QEventLoop>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMessageBox>
#include <QSettings>
#include <QString>
#include <QStringList>
#include <QTimer>
#include <QtGlobal>
#include <QtTest>
#include "CMakeSetupDialog.h"
#include "CatchShow.h"
#include "FirstConfigure.h"
#include "QCMake.h"
using WindowSetupHelper = std::function<void(CMakeSetupDialog*)>;
Q_DECLARE_METATYPE(WindowSetupHelper)
namespace {
void loopSleep(int msecs = 500)
{
QEventLoop loop;
QTimer::singleShot(msecs, &loop, &QEventLoop::quit);
loop.exec();
}
}
CMakeGUITest::CMakeGUITest(CMakeSetupDialog* window, QObject* parent)
: QObject(parent)
, m_window(window)
{
}
void CMakeGUITest::tryConfigure(int expectedResult, int timeout)
{
auto* cmake = this->m_window->findChild<QCMakeThread*>()->cmakeInstance();
bool done = false;
CatchShow catchConfigure;
catchConfigure.setCallback<FirstConfigure>([&done](FirstConfigure* dialog) {
if (done) {
return;
}
done = true;
dialog->findChild<StartCompilerSetup*>()->setCurrentGenerator(
CMAKE_GENERATOR);
dialog->accept();
});
CatchShow catchMessages;
catchMessages.setCallback<QMessageBox>([](QMessageBox* box) {
if (box->text().contains("Build directory does not exist")) {
box->accept();
}
if (box->text().contains("Error in configuration process")) {
box->accept();
}
});
QSignalSpy configureDoneSpy(cmake, &QCMake::configureDone);
QVERIFY(configureDoneSpy.isValid());
QMetaObject::invokeMethod(
this->m_window, [this]() { this->m_window->ConfigureButton->click(); },
Qt::QueuedConnection);
QVERIFY(configureDoneSpy.wait(timeout));
QList<QVariant> configureDoneSignalArguments = configureDoneSpy.takeFirst();
QCOMPARE(configureDoneSignalArguments.at(0).toInt(), expectedResult);
}
void CMakeGUITest::tryGenerate(int expectedResult, int timeout)
{
auto* cmake = this->m_window->findChild<QCMakeThread*>()->cmakeInstance();
CatchShow catchMessages;
catchMessages.setCallback<QMessageBox>([](QMessageBox* box) {
if (box->text().contains("Error in generation process")) {
box->accept();
}
});
QSignalSpy generateDoneSpy(cmake, &QCMake::generateDone);
QVERIFY(generateDoneSpy.isValid());
QMetaObject::invokeMethod(
this->m_window, [this]() { this->m_window->GenerateButton->click(); },
Qt::QueuedConnection);
QVERIFY(generateDoneSpy.wait(timeout));
QList<QVariant> generateDoneSignalArguments = generateDoneSpy.takeFirst();
QCOMPARE(generateDoneSignalArguments.at(0).toInt(), expectedResult);
}
void CMakeGUITest::sourceBinaryArgs()
{
QFETCH(QString, sourceDir);
QFETCH(QString, binaryDir);
// Wait a bit for everything to update
loopSleep();
QCOMPARE(this->m_window->SourceDirectory->text(), sourceDir);
QCOMPARE(this->m_window->BinaryDirectory->currentText(), binaryDir);
}
void CMakeGUITest::sourceBinaryArgs_data()
{
QTest::addColumn<QString>("sourceDir");
QTest::addColumn<QString>("binaryDir");
QTest::newRow("sourceAndBinaryDir")
<< CMakeGUITest_BINARY_DIR "/sourceBinaryArgs-sourceAndBinaryDir/src"
<< CMakeGUITest_BINARY_DIR "/sourceBinaryArgs-sourceAndBinaryDir/build";
QTest::newRow("sourceAndBinaryDirRelative") << CMakeGUITest_BINARY_DIR
"/sourceBinaryArgs-sourceAndBinaryDirRelative/src"
<< CMakeGUITest_BINARY_DIR
"/sourceBinaryArgs-sourceAndBinaryDirRelative/build";
QTest::newRow("sourceDir")
<< CMakeGUITest_BINARY_DIR "/sourceBinaryArgs-sourceDir/src"
<< CMakeGUITest_BINARY_DIR "/sourceBinaryArgs-sourceDir";
QTest::newRow("binaryDir")
<< CMakeGUITest_BINARY_DIR "/sourceBinaryArgs-binaryDir/src"
<< CMakeGUITest_BINARY_DIR "/sourceBinaryArgs-binaryDir/build";
QTest::newRow("noExist") << ""
<< "";
QTest::newRow("noExistConfig")
<< ""
<< CMakeGUITest_BINARY_DIR "/sourceBinaryArgs-noExistConfig/oldbuild";
QTest::newRow("noExistConfigExists")
<< CMakeGUITest_BINARY_DIR "/sourceBinaryArgs-noExistConfigExists/src"
<< CMakeGUITest_BINARY_DIR "/sourceBinaryArgs-noExistConfigExists/build";
}
void CMakeGUITest::simpleConfigure()
{
QFETCH(QString, sourceDir);
QFETCH(QString, binaryDir);
QFETCH(int, expectedResult);
this->m_window->SourceDirectory->setText(sourceDir);
this->m_window->BinaryDirectory->setCurrentText(binaryDir);
// Wait a bit for everything to update
loopSleep();
this->tryConfigure(expectedResult, 1000);
}
void CMakeGUITest::simpleConfigure_data()
{
QTest::addColumn<QString>("sourceDir");
QTest::addColumn<QString>("binaryDir");
QTest::addColumn<int>("expectedResult");
QTest::newRow("success") << CMakeGUITest_BINARY_DIR
"/simpleConfigure-success/src"
<< CMakeGUITest_BINARY_DIR
"/simpleConfigure-success/build"
<< 0;
QTest::newRow("fail") << CMakeGUITest_BINARY_DIR "/simpleConfigure-fail/src"
<< CMakeGUITest_BINARY_DIR
"/simpleConfigure-fail/build"
<< -1;
}
void CMakeGUITest::instrumentation()
{
this->m_window->SourceDirectory->setText(CMakeGUITest_BINARY_DIR
"/instrumentation/src");
this->m_window->BinaryDirectory->setCurrentText(CMakeGUITest_BINARY_DIR
"/instrumentation/build");
// Wait a bit for everything to update
loopSleep();
this->tryConfigure();
this->tryConfigure();
this->tryGenerate();
}
void CMakeGUITest::environment()
{
auto* cmake = this->m_window->findChild<QCMakeThread*>()->cmakeInstance();
this->m_window->SourceDirectory->setText(CMakeGUITest_BINARY_DIR
"/environment/src");
this->m_window->BinaryDirectory->setCurrentText(CMakeGUITest_BINARY_DIR
"/environment/build");
// We are already testing EnvironmentDialog, so just trust that it's
// connected correctly and modify the environment directly.
auto env = cmake->environment();
env.insert("ADDED_VARIABLE", "Added variable");
env.insert("CHANGED_VARIABLE", "Changed variable");
env.remove("REMOVED_VARIABLE");
cmake->setEnvironment(env);
// Wait a bit for everything to update
loopSleep();
this->tryConfigure();
auto penv = QProcessEnvironment::systemEnvironment();
QVERIFY(!penv.contains("ADDED_VARIABLE"));
QCOMPARE(penv.value("KEPT_VARIABLE"), "Kept variable");
QCOMPARE(penv.value("CHANGED_VARIABLE"), "This variable will be changed");
QCOMPARE(penv.value("REMOVED_VARIABLE"), "Removed variable");
}
void CMakeGUITest::presetArg()
{
QFETCH(WindowSetupHelper, setupFunction);
QFETCH(QString, presetName);
QFETCH(QString, sourceDir);
QFETCH(QString, binaryDir);
QFETCH(QCMakePropertyList, properties);
if (setupFunction) {
setupFunction(this->m_window);
}
// Wait a bit for everything to update
loopSleep();
QCOMPARE(this->m_window->Preset->presetName(), presetName);
QCOMPARE(this->m_window->SourceDirectory->text(), sourceDir);
QCOMPARE(this->m_window->BinaryDirectory->currentText(), binaryDir);
auto actualProperties =
this->m_window->CacheValues->cacheModel()->properties();
QCOMPARE(actualProperties.size(), properties.size());
for (int i = 0; i < actualProperties.size(); ++i) {
// operator==() only compares Key, we need to compare Value and Type too
QCOMPARE(actualProperties[i].Key, properties[i].Key);
QCOMPARE(actualProperties[i].Value, properties[i].Value);
QCOMPARE(actualProperties[i].Type, properties[i].Type);
}
}
namespace {
QCMakePropertyList makePresetProperties(QString const& name)
{
return QCMakePropertyList{
QCMakeProperty{
/*Key=*/"FALSE_VARIABLE",
/*Value=*/false,
/*Strings=*/{},
/*Help=*/"",
/*Type=*/QCMakeProperty::BOOL,
/*Advanced=*/false,
},
QCMakeProperty{
/*Key=*/"FILEPATH_VARIABLE",
/*Value=*/
QString::fromLocal8Bit(CMakeGUITest_BINARY_DIR "/%1/src/CMakeLists.txt")
.arg(name),
/*Strings=*/{},
/*Help=*/"",
/*Type=*/QCMakeProperty::FILEPATH,
/*Advanced=*/false,
},
QCMakeProperty{
/*Key=*/"ON_VARIABLE",
/*Value=*/true,
/*Strings=*/{},
/*Help=*/"",
/*Type=*/QCMakeProperty::BOOL,
/*Advanced=*/false,
},
QCMakeProperty{
/*Key=*/"PATH_VARIABLE",
/*Value=*/
QString::fromLocal8Bit(CMakeGUITest_BINARY_DIR "/%1/src").arg(name),
/*Strings=*/{},
/*Help=*/"",
/*Type=*/QCMakeProperty::PATH,
/*Advanced=*/false,
},
QCMakeProperty{
/*Key=*/"SOURCE_DIR_NAME_VARIABLE",
/*Value=*/"src",
/*Strings=*/{},
/*Help=*/"",
/*Type=*/QCMakeProperty::STRING,
/*Advanced=*/false,
},
QCMakeProperty{
/*Key=*/"STRING_VARIABLE",
/*Value=*/"String value",
/*Strings=*/{},
/*Help=*/"",
/*Type=*/QCMakeProperty::STRING,
/*Advanced=*/false,
},
QCMakeProperty{
/*Key=*/"UNINITIALIZED_VARIABLE",
/*Value=*/"Uninitialized value",
/*Strings=*/{},
/*Help=*/"",
/*Type=*/QCMakeProperty::STRING,
/*Advanced=*/false,
},
};
}
}
void CMakeGUITest::presetArg_data()
{
QTest::addColumn<WindowSetupHelper>("setupFunction");
QTest::addColumn<QString>("presetName");
QTest::addColumn<QString>("sourceDir");
QTest::addColumn<QString>("binaryDir");
QTest::addColumn<QCMakePropertyList>("properties");
QTest::newRow("preset") << WindowSetupHelper{} << "ninja"
<< CMakeGUITest_BINARY_DIR "/presetArg-preset/src"
<< CMakeGUITest_BINARY_DIR
"/presetArg-preset/src/build"
<< makePresetProperties("presetArg-preset");
QTest::newRow("presetBinary")
<< WindowSetupHelper{} << "ninja"
<< CMakeGUITest_BINARY_DIR "/presetArg-presetBinary/src"
<< CMakeGUITest_BINARY_DIR "/presetArg-presetBinary/build"
<< makePresetProperties("presetArg-presetBinary");
QTest::newRow("presetBinaryChange")
<< WindowSetupHelper{ [](CMakeSetupDialog* window) {
loopSleep();
window->Preset->setPresetName("ninja2");
} }
<< "ninja2" << CMakeGUITest_BINARY_DIR "/presetArg-presetBinaryChange/src"
<< CMakeGUITest_BINARY_DIR "/presetArg-presetBinaryChange/src/build"
<< makePresetProperties("presetArg-presetBinaryChange");
QTest::newRow("noPresetBinaryChange")
<< WindowSetupHelper{ [](CMakeSetupDialog* window) {
loopSleep();
window->Preset->setPresetName("ninja");
} }
<< "ninja" << CMakeGUITest_BINARY_DIR "/presetArg-noPresetBinaryChange/src"
<< CMakeGUITest_BINARY_DIR "/presetArg-noPresetBinaryChange/src/build"
<< makePresetProperties("presetArg-noPresetBinaryChange");
QTest::newRow("presetConfigExists")
<< WindowSetupHelper{} << "ninja"
<< CMakeGUITest_BINARY_DIR "/presetArg-presetConfigExists/src"
<< CMakeGUITest_BINARY_DIR "/presetArg-presetConfigExists/src/build"
<< makePresetProperties("presetArg-presetConfigExists");
QCMakePropertyList installDirProperties{
QCMakeProperty{
/*Key=*/"CMAKE_INSTALL_PREFIX",
/*Value=*/
QString::fromLocal8Bit(CMakeGUITest_BINARY_DIR
"/presetArg-installDir/src/install"),
/*Strings=*/{},
/*Help=*/"",
/*Type=*/QCMakeProperty::PATH,
/*Advanced=*/false,
},
QCMakeProperty{
/*Key=*/"CMAKE_TOOLCHAIN_FILE",
/*Value=*/
QString::fromLocal8Bit(CMakeGUITest_BINARY_DIR
"/presetArg-installDir/src/toolchain.cmake"),
/*Strings=*/{},
/*Help=*/"",
/*Type=*/QCMakeProperty::FILEPATH,
/*Advanced=*/false,
},
};
QTest::newRow("installDir")
<< WindowSetupHelper{} << "installDir"
<< CMakeGUITest_BINARY_DIR "/presetArg-installDir/src"
<< CMakeGUITest_BINARY_DIR "/presetArg-installDir/src/build"
<< installDirProperties;
QTest::newRow("noExist") << WindowSetupHelper{} << QString{}
<< CMakeGUITest_BINARY_DIR "/presetArg-noExist/src"
<< "" << QCMakePropertyList{};
}
namespace {
void writePresets(QString const& buildDir, QStringList const& names)
{
QJsonArray presets{
QJsonObject{
{ "name", "base" },
{ "generator", "Ninja" },
{ "binaryDir",
QString::fromLocal8Bit("${sourceDir}/%1/${presetName}")
.arg(buildDir) },
{ "hidden", true },
},
};
for (auto const& name : names) {
presets.append(QJsonObject{
{ "name", name },
{ "inherits", QJsonArray{ "base" } },
});
}
QJsonDocument doc{ QJsonObject{
{ "version", 1 },
{ "configurePresets", presets },
} };
QFile presetsFile(CMakeGUITest_BINARY_DIR
"/changingPresets/src/CMakePresets.json");
bool open = presetsFile.open(QIODevice::WriteOnly);
Q_ASSERT(open);
presetsFile.write(doc.toJson());
}
}
void CMakeGUITest::changingPresets()
{
QDir::root().mkpath(CMakeGUITest_BINARY_DIR "/changingPresets/src");
this->m_window->SourceDirectory->setText(CMakeGUITest_BINARY_DIR
"/changingPresets/src");
loopSleep();
QCOMPARE(this->m_window->Preset->presetName(), QString{});
QCOMPARE(this->m_window->Preset->presets().size(), 0);
QCOMPARE(this->m_window->BinaryDirectory->currentText(), "");
QCOMPARE(this->m_window->Preset->isEnabled(), false);
writePresets("build1", { "preset" });
this->m_window->ReloadPresetsButton->click();
loopSleep();
QCOMPARE(this->m_window->Preset->presetName(), QString{});
QCOMPARE(this->m_window->Preset->presets().size(), 1);
QCOMPARE(this->m_window->BinaryDirectory->currentText(), "");
QCOMPARE(this->m_window->Preset->isEnabled(), true);
this->m_window->Preset->setPresetName("preset");
this->m_window->ReloadPresetsButton->click();
loopSleep();
QCOMPARE(this->m_window->Preset->presetName(), "preset");
QCOMPARE(this->m_window->Preset->presets().size(), 1);
QCOMPARE(this->m_window->BinaryDirectory->currentText(),
CMakeGUITest_BINARY_DIR "/changingPresets/src/build1/preset");
QCOMPARE(this->m_window->Preset->isEnabled(), true);
writePresets("build2", { "preset2", "preset" });
this->m_window->ReloadPresetsButton->click();
loopSleep();
QCOMPARE(this->m_window->Preset->presetName(), "preset");
QCOMPARE(this->m_window->Preset->presets().size(), 2);
QCOMPARE(this->m_window->BinaryDirectory->currentText(),
CMakeGUITest_BINARY_DIR "/changingPresets/src/build1/preset");
QCOMPARE(this->m_window->Preset->isEnabled(), true);
writePresets("build3", { "preset2" });
this->m_window->ReloadPresetsButton->click();
loopSleep();
QCOMPARE(this->m_window->Preset->presetName(), QString{});
QCOMPARE(this->m_window->Preset->presets().size(), 1);
QCOMPARE(this->m_window->BinaryDirectory->currentText(),
CMakeGUITest_BINARY_DIR "/changingPresets/src/build1/preset");
QCOMPARE(this->m_window->Preset->isEnabled(), true);
this->m_window->Preset->setPresetName("preset2");
loopSleep();
QCOMPARE(this->m_window->Preset->presetName(), "preset2");
QCOMPARE(this->m_window->Preset->presets().size(), 1);
QCOMPARE(this->m_window->BinaryDirectory->currentText(),
CMakeGUITest_BINARY_DIR "/changingPresets/src/build3/preset2");
QCOMPARE(this->m_window->Preset->isEnabled(), true);
QDir::root().mkpath(CMakeGUITest_BINARY_DIR "/changingPresets/src2");
QFile::copy(CMakeGUITest_BINARY_DIR "/changingPresets/src/CMakePresets.json",
CMakeGUITest_BINARY_DIR
"/changingPresets/src2/CMakePresets.json");
this->m_window->SourceDirectory->setText(CMakeGUITest_BINARY_DIR
"/changingPresets/src2");
loopSleep();
QCOMPARE(this->m_window->Preset->presetName(), QString{});
QCOMPARE(this->m_window->Preset->presets().size(), 1);
QCOMPARE(this->m_window->BinaryDirectory->currentText(),
CMakeGUITest_BINARY_DIR "/changingPresets/src/build3/preset2");
QCOMPARE(this->m_window->Preset->isEnabled(), true);
this->m_window->Preset->setPresetName("preset2");
loopSleep();
QCOMPARE(this->m_window->Preset->presetName(), "preset2");
QCOMPARE(this->m_window->Preset->presets().size(), 1);
QCOMPARE(this->m_window->BinaryDirectory->currentText(),
CMakeGUITest_BINARY_DIR "/changingPresets/src2/build3/preset2");
QCOMPARE(this->m_window->Preset->isEnabled(), true);
QFile(CMakeGUITest_BINARY_DIR "/changingPresets/src2/CMakePresets.json")
.remove();
this->m_window->ReloadPresetsButton->click();
loopSleep();
QCOMPARE(this->m_window->Preset->presetName(), QString{});
QCOMPARE(this->m_window->Preset->presets().size(), 0);
QCOMPARE(this->m_window->BinaryDirectory->currentText(),
CMakeGUITest_BINARY_DIR "/changingPresets/src2/build3/preset2");
QCOMPARE(this->m_window->Preset->isEnabled(), false);
}
namespace {
QString modelValue(QCMakeCacheModel* model, QString const& key)
{
for (auto const& prop : model->properties()) {
if (prop.Key == key) {
return prop.Value.toString();
}
}
return QString{};
}
QString readCacheValue(QString const& buildDir, QString const& key)
{
QFile cache(buildDir + "/CMakeCache.txt");
if (!cache.open(QIODevice::ReadOnly | QIODevice::Text)) {
return QString{};
}
QString const prefix = key + ":";
while (!cache.atEnd()) {
QString const line = QString::fromLocal8Bit(cache.readLine()).trimmed();
if (line.startsWith(prefix)) {
int const eq = line.indexOf('=');
if (eq >= 0) {
return line.mid(eq + 1);
}
}
}
return QString{};
}
}
void CMakeGUITest::presetApplyOrdering()
{
QFETCH(QString, scenario);
QFETCH(QString, buildDir);
QFETCH(QString, expectedValue);
auto* cmake = this->m_window->findChild<QCMakeThread*>()->cmakeInstance();
// Let the initial cache load from the preconfigured build settle.
loopSleep();
QSignalSpy configureDoneSpy(cmake, &QCMake::configureDone);
QVERIFY(configureDoneSpy.isValid());
// Select and configure in one main-thread run, with no yield between, to
// hit the race.
this->m_window->Preset->setPresetName("raceA");
if (scenario == "replace" || scenario == "replaceCycle") {
this->m_window->Preset->setPresetName("raceB");
}
if (scenario == "replaceCycle") {
this->m_window->Preset->setPresetName("raceA");
}
// Dependent actions must be off the instant the request is submitted.
QVERIFY(!this->m_window->ConfigureButton->isEnabled());
QVERIFY(!this->m_window->GenerateButton->isEnabled());
// Activating while a preset applies must not configure with the stale model.
this->m_window->ConfigureButton->click();
QCOMPARE(configureDoneSpy.count(), 0);
// Gate lifts only once the accepted completion has installed the model.
QTRY_VERIFY(this->m_window->ConfigureButton->isEnabled());
// The click during the pending window did nothing.
QCOMPARE(configureDoneSpy.count(), 0);
// Model holds the winning preset, not the preconfigured value.
QCOMPARE(modelValue(this->m_window->CacheValues->cacheModel(), "RACE_VALUE"),
expectedValue);
// A real configure writes that value through to the cache.
this->tryConfigure();
QCOMPARE(readCacheValue(buildDir, "RACE_VALUE"), expectedValue);
}
void CMakeGUITest::presetApplyOrdering_data()
{
QTest::addColumn<QString>("scenario");
QTest::addColumn<QString>("buildDir");
QTest::addColumn<QString>("expectedValue");
QTest::newRow("apply") << "apply"
<< CMakeGUITest_BINARY_DIR
"/presetApplyOrdering-apply/build"
<< "presetA";
QTest::newRow("replace") << "replace"
<< CMakeGUITest_BINARY_DIR
"/presetApplyOrdering-replace/build"
<< "presetB";
QTest::newRow("replaceCycle")
<< "replaceCycle"
<< CMakeGUITest_BINARY_DIR "/presetApplyOrdering-replaceCycle/build"
<< "presetA";
}
void CMakeGUITest::presetApplyStartup()
{
QFETCH(QString, requestedPreset);
QFETCH(bool, available);
// The deferred gate must have the actions off at startup, before presets
// load. Deterministic: pending was set on the main thread before the event
// loop processed any worker result.
QVERIFY(!this->m_window->ConfigureButton->isEnabled());
QVERIFY(!this->m_window->GenerateButton->isEnabled());
// Gate lifts once presets resolve, applied or not.
QTRY_VERIFY(this->m_window->ConfigureButton->isEnabled());
QVERIFY(this->m_window->GenerateButton->isEnabled());
if (available) {
QCOMPARE(this->m_window->Preset->presetName(), requestedPreset);
QCOMPARE(
modelValue(this->m_window->CacheValues->cacheModel(), "RACE_VALUE"),
QString("presetA"));
} else {
QVERIFY(this->m_window->Preset->presetName() != requestedPreset);
}
}
void CMakeGUITest::presetApplyStartup_data()
{
QTest::addColumn<QString>("requestedPreset");
QTest::addColumn<bool>("available");
QTest::newRow("available") << "raceA" << true;
QTest::newRow("unavailable") << "doesNotExist" << false;
}
void SetupDefaultQSettings()
{
QSettings::setDefaultFormat(QSettings::IniFormat);
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope,
QString::fromLocal8Bit(qgetenv("CMake_GUI_CONFIG_DIR")));
}
int CMakeGUIExec(CMakeSetupDialog* window)
{
auto nameArray = qgetenv("CMake_GUI_TEST_NAME");
auto name = QString::fromLocal8Bit(nameArray);
if (name.isEmpty()) {
return QApplication::exec();
}
QStringList args{ "CMakeGUITest", name };
CMakeGUITest obj(window);
return QTest::qExec(&obj, args);
}