// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "testrunner.h" #include "autotestconstants.h" #include "autotestplugin.h" #include "autotesttr.h" #include "testoutputreader.h" #include "testprojectsettings.h" #include "testresultspane.h" #include "testrunconfiguration.h" #include "testtreeitem.h" #include "testtreemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Core; using namespace ProjectExplorer; using namespace Tasking; using namespace Utils; namespace Autotest { namespace Internal { static Q_LOGGING_CATEGORY(runnerLog, "qtc.autotest.testrunner", QtWarningMsg) static TestRunner *s_instance = nullptr; TestRunner *TestRunner::instance() { return s_instance; } TestRunner::TestRunner() { s_instance = this; m_cancelTimer.setSingleShot(true); connect(&m_cancelTimer, &QTimer::timeout, this, [this] { cancelCurrent(Timeout); }); connect(this, &TestRunner::requestStopTestRun, this, [this] { cancelCurrent(UserCanceled); }); connect(BuildManager::instance(), &BuildManager::buildQueueFinished, this, &TestRunner::onBuildQueueFinished); connect(&m_taskTreeRunner, &TaskTreeRunner::aboutToStart, this, [this](TaskTree *taskTree) { auto progress = new TaskProgress(taskTree); progress->setDisplayName(Tr::tr("Running Tests")); progress->setAutoStopOnCancel(false); using namespace std::chrono_literals; progress->setHalfLifeTimePerTask(10s); connect(progress, &TaskProgress::canceled, this, [this, progress] { // Progress was a child of task tree which is going to be deleted directly. // Unwind properly. progress->setParent(nullptr); progress->deleteLater(); cancelCurrent(UserCanceled); }); if (testSettings().popupOnStart()) popupResultsPane(); }); connect(&m_taskTreeRunner, &TaskTreeRunner::done, this, &TestRunner::onFinished); } TestRunner::~TestRunner() { qDeleteAll(m_selectedTests); m_selectedTests.clear(); s_instance = nullptr; } void TestRunner::runTest(TestRunMode mode, const ITestTreeItem *item) { QTC_ASSERT(!isTestRunning(), return); ITestConfiguration *configuration = item->asConfiguration(mode); if (configuration) runTests(mode, {configuration}); } static QString processInformation(const Process *proc) { QTC_ASSERT(proc, return {}); const CommandLine command = proc->commandLine(); QString information("\nCommand line: " + command.executable().toUserOutput() + ' ' + command.arguments()); QStringList important = { "PATH" }; if (HostOsInfo::isLinuxHost()) important.append("LD_LIBRARY_PATH"); else if (HostOsInfo::isMacHost()) important.append({ "DYLD_LIBRARY_PATH", "DYLD_FRAMEWORK_PATH" }); const Environment &environment = proc->environment(); for (const QString &var : important) information.append('\n' + var + ": " + environment.value(var)); return information; } static QString rcInfo(const ITestConfiguration * const config) { if (config->testBase()->type() == ITestBase::Tool) return {}; const TestConfiguration *current = static_cast(config); QString info; if (current->isDeduced()) info = Tr::tr("\nRun configuration: deduced from \"%1\""); else info = Tr::tr("\nRun configuration: \"%1\""); return info.arg(current->runConfigDisplayName()); } static QString constructOmittedDetailsString(const QStringList &omitted) { return Tr::tr("Omitted the following arguments specified on the run " "configuration page for \"%1\":") + '\n' + omitted.join('\n'); } static QString constructOmittedVariablesDetailsString(const EnvironmentItems &diff) { auto removedVars = Utils::transform(diff, [](const EnvironmentItem &it) { return it.name; }); return Tr::tr("Omitted the following environment variables for \"%1\":") + '\n' + removedVars.join('\n'); } void TestRunner::cancelCurrent(TestRunner::CancelReason reason) { if (reason == KitChanged) reportResult(ResultType::MessageWarn, Tr::tr("Current kit has changed. Canceling test run.")); else if (reason == Timeout) reportResult(ResultType::MessageFatal, Tr::tr("Test case canceled due to timeout.\nMaybe raise the timeout?")); else if (reason == UserCanceled) reportResult(ResultType::MessageFatal, Tr::tr("Test run canceled by user.")); m_taskTreeRunner.reset(); onFinished(); } void TestRunner::runTests(TestRunMode mode, const QList &selectedTests) { QTC_ASSERT(!isTestRunning(), return); qDeleteAll(m_selectedTests); m_selectedTests = selectedTests; m_skipTargetsCheck = false; m_runMode = mode; if (mode != TestRunMode::RunAfterBuild && projectExplorerSettings().buildBeforeDeploy != BuildBeforeRunMode::Off && !projectExplorerSettings().saveBeforeBuild) { if (!ProjectExplorerPlugin::saveModifiedFiles()) return; } emit testRunStarted(); // clear old log and output pane TestResultsPane::instance()->clearContents(); TestTreeModel::instance()->clearFailedMarks(); if (m_selectedTests.isEmpty()) { reportResult(ResultType::MessageWarn, Tr::tr("No tests selected. Canceling test run.")); onFinished(); return; } Project *project = m_selectedTests.first()->project(); if (!project) { reportResult(ResultType::MessageWarn, Tr::tr("Project is null. Canceling test run.\n" "Only desktop kits are supported. Make sure the " "currently active kit is a desktop kit.")); onFinished(); return; } m_targetConnect = connect(project, &Project::activeTargetChanged, this, [this] { cancelCurrent(KitChanged); }); if (projectExplorerSettings().buildBeforeDeploy == BuildBeforeRunMode::Off || mode == TestRunMode::DebugWithoutDeploy || mode == TestRunMode::RunWithoutDeploy || mode == TestRunMode::RunAfterBuild) { runOrDebugTests(); return; } Target *target = project->activeTarget(); if (target && BuildConfigurationFactory::find(target)) { buildProject(project); } else { reportResult(ResultType::MessageFatal, Tr::tr("Project is not configured. Canceling test run.")); onFinished(); } } static QString firstNonEmptyTestCaseTarget(const TestConfiguration *config) { return Utils::findOrDefault(config->internalTargets(), [](const QString &internalTarget) { return !internalTarget.isEmpty(); }); } static RunConfiguration *getRunConfiguration(const QString &buildTargetKey) { const Project *project = ProjectManager::startupProject(); if (!project) return nullptr; const Target *target = project->activeTarget(); if (!target) return nullptr; RunConfiguration *runConfig = nullptr; const QList runConfigurations = Utils::filtered(target->runConfigurations(), [](const RunConfiguration *rc) { return !rc->runnable().command.isEmpty(); }); const ChoicePair oldChoice = cachedChoiceFor(buildTargetKey); if (!oldChoice.executable.isEmpty()) { runConfig = Utils::findOrDefault(runConfigurations, [&oldChoice](const RunConfiguration *rc) { return oldChoice.matches(rc); }); if (runConfig) return runConfig; } if (runConfigurations.size() == 1) return runConfigurations.first(); RunConfigurationSelectionDialog dialog(buildTargetKey, ICore::dialogParent()); if (dialog.exec() == QDialog::Accepted) { const QString dName = dialog.displayName(); if (dName.isEmpty()) return nullptr; // run configuration has been selected - fill config based on this one.. const FilePath exe = FilePath::fromString(dialog.executable()); runConfig = Utils::findOr(runConfigurations, nullptr, [&dName, &exe](const RunConfiguration *rc) { if (rc->displayName() != dName) return false; return rc->runnable().command.executable() == exe; }); if (runConfig && dialog.rememberChoice()) cacheRunConfigChoice(buildTargetKey, ChoicePair(dName, exe)); } return runConfig; } int TestRunner::precheckTestConfigurations() { const bool omitWarnings = testSettings().omitRunConfigWarn(); int testCaseCount = 0; for (ITestConfiguration *itc : std::as_const(m_selectedTests)) { if (itc->testBase()->type() == ITestBase::Tool) { if (itc->project()) { testCaseCount += itc->testCaseCount(); } else { reportResult(ResultType::MessageWarn, Tr::tr("Project is null for \"%1\". Removing from test run.\n" "Check the test environment.").arg(itc->displayName())); } continue; } TestConfiguration *config = static_cast(itc); config->completeTestInformation(TestRunMode::Run); if (config->project()) { testCaseCount += config->testCaseCount(); if (!omitWarnings && config->isDeduced()) { QString message = Tr::tr( "Project's run configuration was deduced for \"%1\".\n" "This might cause trouble during execution.\n" "(deduced from \"%2\")"); message = message.arg(config->displayName(), config->runConfigDisplayName()); reportResult(ResultType::MessageWarn, message); } } else { reportResult(ResultType::MessageWarn, Tr::tr("Project is null for \"%1\". Removing from test run.\n" "Check the test environment.").arg(config->displayName())); } } return testCaseCount; } void TestRunner::onBuildSystemUpdated() { Target *target = ProjectManager::startupTarget(); if (QTC_GUARD(target)) disconnect(target, &Target::buildSystemUpdated, this, &TestRunner::onBuildSystemUpdated); if (!m_skipTargetsCheck) { m_skipTargetsCheck = true; runOrDebugTests(); } } void TestRunner::runTestsHelper() { QList toBeRemoved; bool projectChanged = false; for (ITestConfiguration *itc : std::as_const(m_selectedTests)) { if (itc->testBase()->type() == ITestBase::Tool) { if (itc->project() != ProjectManager::startupProject()) { projectChanged = true; toBeRemoved.append(itc); } continue; } TestConfiguration *config = static_cast(itc); config->completeTestInformation(TestRunMode::Run); if (!config->project()) { projectChanged = true; toBeRemoved.append(config); } else if (!config->hasExecutable()) { if (auto rc = getRunConfiguration(firstNonEmptyTestCaseTarget(config))) config->setOriginalRunConfiguration(rc); else toBeRemoved.append(config); } } for (ITestConfiguration *config : toBeRemoved) m_selectedTests.removeOne(config); qDeleteAll(toBeRemoved); toBeRemoved.clear(); if (m_selectedTests.isEmpty()) { QString mssg = projectChanged ? Tr::tr("Startup project has changed. Canceling test run.") : Tr::tr("No test cases left for execution. Canceling test run."); reportResult(ResultType::MessageWarn, mssg); onFinished(); return; } const int testCaseCount = precheckTestConfigurations(); Q_UNUSED(testCaseCount) // TODO: may be useful for fine-grained progress reporting, when fixed struct TestStorage { std::unique_ptr m_outputReader; }; const LoopList iterator(m_selectedTests); const Storage storage; const auto onSetup = [this, iterator, storage](Process &process) { ITestConfiguration *config = *iterator; QTC_ASSERT(config, return SetupResult::StopWithError); if (!config->project()) return SetupResult::StopWithSuccess; if (config->testExecutable().isEmpty()) { reportResult(ResultType::MessageFatal, Tr::tr("Executable path is empty. (%1)").arg(config->displayName())); return SetupResult::StopWithSuccess; } TestStorage *testStorage = storage.activeStorage(); QTC_ASSERT(testStorage, return SetupResult::StopWithError); testStorage->m_outputReader.reset(config->createOutputReader(&process)); QTC_ASSERT(testStorage->m_outputReader, return SetupResult::StopWithError); connect(testStorage->m_outputReader.get(), &TestOutputReader::newResult, this, &TestRunner::testResultReady); connect(testStorage->m_outputReader.get(), &TestOutputReader::newOutputLineAvailable, TestResultsPane::instance(), &TestResultsPane::addOutputLine); CommandLine command{config->testExecutable(), {}}; if (config->testBase()->type() == ITestBase::Framework) { TestConfiguration *current = static_cast(config); QStringList omitted; command.addArgs(current->argumentsForTestRunner(&omitted).join(' '), CommandLine::Raw); if (!omitted.isEmpty()) { const QString &details = constructOmittedDetailsString(omitted); reportResult(ResultType::MessageWarn, details.arg(current->displayName())); } } else { TestToolConfiguration *current = static_cast(config); command.setArguments(current->commandLine().arguments()); } process.setCommand(command); process.setWorkingDirectory(config->workingDirectory()); const Environment &original = config->environment(); Environment environment = config->filteredEnvironment(original); const EnvironmentItems removedVariables = Utils::filtered( original.diff(environment), [](const EnvironmentItem &it) { return it.operation == EnvironmentItem::Unset; }); if (!removedVariables.isEmpty()) { const QString &details = constructOmittedVariablesDetailsString(removedVariables) .arg(config->displayName()); reportResult(ResultType::MessageWarn, details); } process.setEnvironment(environment); if (testSettings().useTimeout()) { m_cancelTimer.setInterval(testSettings().timeout()); m_cancelTimer.start(); } qCInfo(runnerLog) << "Command:" << process.commandLine().executable(); qCInfo(runnerLog) << "Arguments:" << process.commandLine().arguments(); qCInfo(runnerLog) << "Working directory:" << process.workingDirectory(); qCDebug(runnerLog) << "Environment:" << process.environment().toStringList(); return SetupResult::Continue; }; const auto onDone = [this, iterator, storage](const Process &process) { ITestConfiguration *config = *iterator; TestStorage *testStorage = storage.activeStorage(); QTC_ASSERT(testStorage, return); if (process.result() == ProcessResult::StartFailed) { reportResult(ResultType::MessageFatal, Tr::tr("Failed to start test for project \"%1\".").arg(config->displayName()) + processInformation(&process) + rcInfo(config)); } if (testStorage->m_outputReader) testStorage->m_outputReader->onDone(process.exitCode()); if (process.exitStatus() == QProcess::CrashExit) { if (testStorage->m_outputReader) testStorage->m_outputReader->reportCrash(); reportResult(ResultType::MessageFatal, Tr::tr("Test for project \"%1\" crashed.").arg(config->displayName()) + processInformation(&process) + rcInfo(config)); } else if (testStorage->m_outputReader && !testStorage->m_outputReader->hadValidOutput()) { reportResult(ResultType::MessageFatal, Tr::tr("Test for project \"%1\" did not produce any expected output.") .arg(config->displayName()) + processInformation(&process) + rcInfo(config)); } if (testStorage->m_outputReader) { const int disabled = testStorage->m_outputReader->disabledTests(); if (disabled > 0) emit hadDisabledTests(disabled); if (testStorage->m_outputReader->hasSummary()) emit reportSummary(testStorage->m_outputReader->id(), testStorage->m_outputReader->summary()); testStorage->m_outputReader->resetCommandlineColor(); } }; const Group root { finishAllAndSuccess, iterator, Group { storage, ProcessTask(onSetup, onDone) } }; m_taskTreeRunner.start(root); } static void processOutput(TestOutputReader *outputreader, const QString &msg, OutputFormat format) { QByteArray message = msg.toUtf8(); switch (format) { case OutputFormat::StdErrFormat: case OutputFormat::StdOutFormat: case OutputFormat::DebugFormat: { static const QByteArray gdbSpecialOut = "Qt: gdb: -nograb added to command-line options.\n" "\t Use the -dograb option to enforce grabbing."; if (message.startsWith(gdbSpecialOut)) message = message.mid(gdbSpecialOut.length() + 1); message.chop(1); // all messages have an additional \n at the end for (const auto &line : message.split('\n')) { if (format == OutputFormat::StdOutFormat) outputreader->processStdOutput(line); else outputreader->processStdError(line); } break; } default: break; // channels we're not caring about } } void TestRunner::debugTests() { // TODO improve to support more than one test configuration QTC_ASSERT(m_selectedTests.size() == 1, onFinished(); return); ITestConfiguration *itc = m_selectedTests.first(); QTC_ASSERT(itc->testBase()->type() == ITestBase::Framework, onFinished(); return); TestConfiguration *config = static_cast(itc); config->completeTestInformation(TestRunMode::Debug); if (!config->project()) { reportResult(ResultType::MessageWarn, Tr::tr("Startup project has changed. Canceling test run.")); onFinished(); return; } if (!config->hasExecutable()) { if (auto *rc = getRunConfiguration(firstNonEmptyTestCaseTarget(config))) config->completeTestInformation(rc, TestRunMode::Debug); } if (!config->runConfiguration()) { reportResult(ResultType::MessageFatal, Tr::tr("Failed to get run configuration.")); onFinished(); return; } const FilePath &commandFilePath = config->executableFilePath(); if (commandFilePath.isEmpty()) { reportResult(ResultType::MessageFatal, Tr::tr("Could not find command \"%1\". (%2)") .arg(config->executableFilePath().toString(), config->displayName())); onFinished(); return; } auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE); runControl->copyDataFromRunConfiguration(config->runConfiguration()); QStringList omitted; ProcessRunData inferior = config->runnable(); inferior.command.setExecutable(commandFilePath); const QStringList args = config->argumentsForTestRunner(&omitted); inferior.command.setArguments(ProcessArgs::joinArgs(args)); if (!omitted.isEmpty()) { const QString &details = constructOmittedDetailsString(omitted); reportResult(ResultType::MessageWarn, details.arg(config->displayName())); } Environment original(inferior.environment); inferior.environment = config->filteredEnvironment(original); const EnvironmentItems removedVariables = Utils::filtered( original.diff(inferior.environment), [](const EnvironmentItem &it) { return it.operation == EnvironmentItem::Unset; }); if (!removedVariables.isEmpty()) { const QString &details = constructOmittedVariablesDetailsString(removedVariables) .arg(config->displayName()); reportResult(ResultType::MessageWarn, details); } auto debugger = new Debugger::DebuggerRunTool(runControl); debugger->setInferior(inferior); debugger->setRunControlName(config->displayName()); bool useOutputProcessor = true; if (Target *targ = config->project()->activeTarget()) { if (Debugger::DebuggerKitAspect::engineType(targ->kit()) == Debugger::CdbEngineType) { reportResult(ResultType::MessageWarn, Tr::tr("Unable to display test results when using CDB.")); useOutputProcessor = false; } } if (useOutputProcessor) { TestOutputReader *outputreader = config->createOutputReader(nullptr); connect(outputreader, &TestOutputReader::newResult, this, &TestRunner::testResultReady); outputreader->setId(inferior.command.executable().toString()); connect(outputreader, &TestOutputReader::newOutputLineAvailable, TestResultsPane::instance(), &TestResultsPane::addOutputLine); connect(runControl, &RunControl::appendMessage, this, [outputreader](const QString &msg, OutputFormat format) { processOutput(outputreader, msg, format); }); connect(runControl, &RunControl::stopped, outputreader, &QObject::deleteLater); } m_stopDebugConnect = connect(this, &TestRunner::requestStopTestRun, runControl, &RunControl::initiateStop); connect(runControl, &RunControl::stopped, this, &TestRunner::onFinished); ProjectExplorerPlugin::startRunControl(runControl); if (useOutputProcessor && testSettings().popupOnStart()) popupResultsPane(); } static bool executablesEmpty() { Target *target = ProjectManager::startupTarget(); const QList configs = target->runConfigurations(); QTC_ASSERT(!configs.isEmpty(), return false); if (auto execAspect = configs.first()->aspect()) return execAspect->executable().isEmpty(); return false; } void TestRunner::runOrDebugTests() { if (!m_skipTargetsCheck) { if (executablesEmpty()) { m_skipTargetsCheck = true; Target *target = ProjectManager::startupTarget(); QTimer::singleShot(5000, this, [this, target = QPointer(target)] { if (target) { disconnect(target, &Target::buildSystemUpdated, this, &TestRunner::onBuildSystemUpdated); } runOrDebugTests(); }); connect(target, &Target::buildSystemUpdated, this, &TestRunner::onBuildSystemUpdated); return; } } switch (m_runMode) { case TestRunMode::Run: case TestRunMode::RunWithoutDeploy: case TestRunMode::RunAfterBuild: runTestsHelper(); return; case TestRunMode::Debug: case TestRunMode::DebugWithoutDeploy: debugTests(); return; default: break; } QTC_ASSERT(false, qDebug() << "Unexpected run mode" << int(m_runMode)); // unexpected run mode onFinished(); } void TestRunner::buildProject(Project *project) { BuildManager *buildManager = BuildManager::instance(); m_buildConnect = connect(this, &TestRunner::requestStopTestRun, buildManager, &BuildManager::cancel); connect(buildManager, &BuildManager::buildQueueFinished, this, &TestRunner::buildFinished); BuildManager::buildProjectWithDependencies(project); if (!BuildManager::isBuilding()) buildFinished(false); } void TestRunner::buildFinished(bool success) { disconnect(m_buildConnect); BuildManager *buildManager = BuildManager::instance(); disconnect(buildManager, &BuildManager::buildQueueFinished, this, &TestRunner::buildFinished); if (success) { runOrDebugTests(); return; } reportResult(ResultType::MessageFatal, Tr::tr("Build failed. Canceling test run.")); onFinished(); } static RunAfterBuildMode runAfterBuild() { Project *project = ProjectManager::startupProject(); if (!project) return RunAfterBuildMode::None; if (!project->namedSettings(Constants::SK_USE_GLOBAL).isValid()) return testSettings().runAfterBuildMode(); TestProjectSettings *projectSettings = Internal::projectSettings(project); return projectSettings->useGlobalSettings() ? testSettings().runAfterBuildMode() : projectSettings->runAfterBuild(); } void TestRunner::onBuildQueueFinished(bool success) { if (isTestRunning() || !m_selectedTests.isEmpty()) // paranoia! return; if (!success || m_runMode != TestRunMode::None) return; RunAfterBuildMode mode = runAfterBuild(); if (mode == RunAfterBuildMode::None) return; auto testTreeModel = TestTreeModel::instance(); if (!testTreeModel->hasTests()) return; const QList tests = mode == RunAfterBuildMode::All ? testTreeModel->getAllTestCases() : testTreeModel->getSelectedTests(); runTests(TestRunMode::RunAfterBuild, tests); } void TestRunner::onFinished() { m_taskTreeRunner.reset(); disconnect(m_stopDebugConnect); disconnect(m_targetConnect); qDeleteAll(m_selectedTests); m_selectedTests.clear(); m_cancelTimer.stop(); m_runMode = TestRunMode::None; emit testRunFinished(); } void TestRunner::reportResult(ResultType type, const QString &description) { TestResult result({}, {}); result.setResult(type); result.setDescription(description); emit testResultReady(result); } /*************************************************************************************************/ RunConfigurationSelectionDialog::RunConfigurationSelectionDialog(const QString &buildTargetKey, QWidget *parent) : QDialog(parent) { setWindowTitle(Tr::tr("Select Run Configuration")); QString details = Tr::tr("Could not determine which run configuration to choose for running tests"); if (!buildTargetKey.isEmpty()) details.append(QString(" (%1)").arg(buildTargetKey)); m_details = new QLabel(details, this); m_rcCombo = new QComboBox(this); m_rememberCB = new QCheckBox(Tr::tr("Remember choice. Cached choices can be reset by switching " "projects or using the option to clear the cache."), this); m_executable = new QLabel(this); m_arguments = new QLabel(this); m_workingDir = new QLabel(this); m_buttonBox = new QDialogButtonBox(this); m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); auto formLayout = new QFormLayout; formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); formLayout->addRow(m_details); formLayout->addRow(Tr::tr("Run Configuration:"), m_rcCombo); formLayout->addRow(m_rememberCB); formLayout->addRow(Layouting::createHr(this)); formLayout->addRow(Tr::tr("Executable:"), m_executable); formLayout->addRow(Tr::tr("Arguments:"), m_arguments); formLayout->addRow(Tr::tr("Working Directory:"), m_workingDir); // TODO Device support auto vboxLayout = new QVBoxLayout(this); vboxLayout->addLayout(formLayout); vboxLayout->addStretch(); vboxLayout->addWidget(Layouting::createHr(this)); vboxLayout->addWidget(m_buttonBox); connect(m_rcCombo, &QComboBox::currentTextChanged, this, &RunConfigurationSelectionDialog::updateLabels); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); populate(); } QString RunConfigurationSelectionDialog::displayName() const { return m_rcCombo ? m_rcCombo->currentText() : QString(); } QString RunConfigurationSelectionDialog::executable() const { return m_executable ? m_executable->text() : QString(); } bool RunConfigurationSelectionDialog::rememberChoice() const { return m_rememberCB ? m_rememberCB->isChecked() : false; } void RunConfigurationSelectionDialog::populate() { m_rcCombo->addItem({}, QStringList{{}, {}, {}}); // empty default if (auto project = ProjectManager::startupProject()) { if (auto target = project->activeTarget()) { for (RunConfiguration *rc : target->runConfigurations()) { auto runnable = rc->runnable(); const QStringList rcDetails = { runnable.command.executable().toString(), runnable.command.arguments(), runnable.workingDirectory.toString() }; m_rcCombo->addItem(rc->displayName(), rcDetails); } } } } void RunConfigurationSelectionDialog::updateLabels() { int i = m_rcCombo->currentIndex(); const QStringList values = m_rcCombo->itemData(i).toStringList(); QTC_ASSERT(values.size() == 3, return); m_executable->setText(values.at(0)); m_arguments->setText(values.at(1)); m_workingDir->setText(values.at(2)); } } // namespace Internal } // namespace Autotest