// 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 "testresultspane.h" #include "autotesticons.h" #include "autotestplugin.h" #include "autotesttr.h" #include "itestframework.h" #include "testeditormark.h" #include "testresultdelegate.h" #include "testresultmodel.h" #include "testresultmodel.h" #include "testrunner.h" #include "testsettings.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 #include #include #include #include #include using namespace Core; using namespace Utils; namespace Autotest::Internal { ResultsTreeView::ResultsTreeView(QWidget *parent) : TreeView(parent) { setUniformRowHeights(false); setAttribute(Qt::WA_MacShowFocusRect, false); setFrameStyle(NoFrame); } void ResultsTreeView::keyPressEvent(QKeyEvent *event) { if (event->matches(QKeySequence::Copy)) { emit copyShortcutTriggered(); event->accept(); } TreeView::keyPressEvent(event); } TestResultsPane::TestResultsPane(QObject *parent) : IOutputPane(parent), m_context(new IContext(this)) { setId("TestResults"); setDisplayName(Tr::tr("Test Results")); setPriorityInStatusBar(-30); m_outputWidget = new QStackedWidget; QWidget *visualOutputWidget = new QWidget; m_outputWidget->addWidget(visualOutputWidget); QVBoxLayout *outputLayout = new QVBoxLayout; outputLayout->setContentsMargins(0, 0, 0, 0); outputLayout->setSpacing(0); visualOutputWidget->setLayout(outputLayout); QPalette pal; pal.setColor(QPalette::Window, creatorColor(Theme::InfoBarBackground)); pal.setColor(QPalette::WindowText, creatorColor(Theme::InfoBarText)); m_summaryWidget = new QFrame; m_summaryWidget->setPalette(pal); m_summaryWidget->setAutoFillBackground(true); QHBoxLayout *layout = new QHBoxLayout; layout->setContentsMargins(6, 6, 6, 6); m_summaryWidget->setLayout(layout); m_summaryLabel = new QLabel; m_summaryLabel->setPalette(pal); layout->addWidget(m_summaryLabel); m_summaryWidget->setVisible(false); outputLayout->addWidget(m_summaryWidget); m_treeView = new ResultsTreeView(visualOutputWidget); m_treeView->setHeaderHidden(true); m_treeView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); pal = m_treeView->palette(); pal.setColor(QPalette::Base, pal.window().color()); m_treeView->setPalette(pal); m_model = new TestResultModel(this); m_filterModel = new TestResultFilterModel(m_model, this); m_filterModel->setDynamicSortFilter(true); m_filterModel->setRecursiveFilteringEnabled(true); m_treeView->setModel(m_filterModel); TestResultDelegate *trd = new TestResultDelegate(this); m_treeView->setItemDelegate(trd); outputLayout->addWidget(ItemViewFind::createSearchableWrapper(m_treeView)); m_textOutput = new Core::OutputWindow(Core::Context("AutoTest.TextOutput"), "AutoTest.TextOutput.Filter"); m_textOutput->setBaseFont(TextEditor::TextEditorSettings::fontSettings().font()); m_textOutput->setWordWrapMode(QTextOption::WordWrap); m_textOutput->setReadOnly(true); m_outputWidget->addWidget(m_textOutput); setupFilterUi("AutoTest.TextOutput.Filter", "Autotest::Internal::TestResultsPane"); setupContext("AutoTest.TextOutput", m_textOutput); setFilteringEnabled(false); setZoomButtonsEnabled(false); connect(this, &IOutputPane::zoomInRequested, m_textOutput, &Core::OutputWindow::zoomIn); connect(this, &IOutputPane::zoomOutRequested, m_textOutput, &Core::OutputWindow::zoomOut); connect(this, &IOutputPane::resetZoomRequested, m_textOutput, &Core::OutputWindow::resetZoom); connect(this, &IOutputPane::fontChanged, m_textOutput, &OutputWindow::setBaseFont); createToolButtons(); connect(m_treeView, &TreeView::activated, this, &TestResultsPane::onItemActivated); connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged, trd, &TestResultDelegate::currentChanged); connect(m_treeView, &TreeView::customContextMenuRequested, this, &TestResultsPane::onCustomContextMenuRequested); connect(m_treeView, &ResultsTreeView::copyShortcutTriggered, this, [this] { onCopyItemTriggered(getTestResult(m_treeView->currentIndex())); }); connect(m_model, &TestResultModel::requestExpansion, this, [this](const QModelIndex &idx) { m_treeView->expandRecursively(m_filterModel->mapFromSource(idx)); }); connect(TestRunner::instance(), &TestRunner::testRunStarted, this, &TestResultsPane::onTestRunStarted); connect(TestRunner::instance(), &TestRunner::testRunFinished, this, &TestResultsPane::onTestRunFinished); connect(TestRunner::instance(), &TestRunner::testResultReady, this, &TestResultsPane::addTestResult); connect(TestRunner::instance(), &TestRunner::hadDisabledTests, m_model, &TestResultModel::raiseDisabledTests); visualOutputWidget->installEventFilter(this); connect(SessionManager::instance(), &SessionManager::sessionLoaded, this, &TestResultsPane::onSessionLoaded); connect(SessionManager::instance(), &SessionManager::aboutToSaveSession, this, &TestResultsPane::onAboutToSaveSession); } void TestResultsPane::createToolButtons() { m_expandCollapse = new QToolButton(m_treeView); m_expandCollapse->setIcon(Utils::Icons::EXPAND_ALL_TOOLBAR.icon()); m_expandCollapse->setToolTip(Tr::tr("Expand All")); m_expandCollapse->setCheckable(true); m_expandCollapse->setChecked(false); connect(m_expandCollapse, &QToolButton::clicked, this, [this](bool checked) { if (checked) m_treeView->expandAll(); else m_treeView->collapseAll(); }); m_runAll = new QToolButton(m_treeView); m_runAll->setDefaultAction(ProxyAction::proxyActionWithIcon( ActionManager::command(Constants::ACTION_RUN_ALL_ID)->action(), Utils::Icons::RUN_SMALL_TOOLBAR.icon())); m_runSelected = new QToolButton(m_treeView); m_runSelected->setDefaultAction(ProxyAction::proxyActionWithIcon( ActionManager::command(Constants::ACTION_RUN_SELECTED_ID)->action(), Utils::Icons::RUN_SELECTED_TOOLBAR.icon())); m_runFailed = new QToolButton(m_treeView); m_runFailed->setDefaultAction(ProxyAction::proxyActionWithIcon( ActionManager::command(Constants::ACTION_RUN_FAILED_ID)->action(), Icons::RUN_FAILED_TOOLBAR.icon())); m_runFile = new QToolButton(m_treeView); m_runFile->setDefaultAction(ProxyAction::proxyActionWithIcon( ActionManager::command(Constants::ACTION_RUN_FILE_ID)->action(), Utils::Icons::RUN_FILE_TOOLBAR.icon())); m_stopTestRun = new QToolButton(m_treeView); m_stopTestRun->setIcon(Utils::Icons::STOP_SMALL_TOOLBAR.icon()); m_stopTestRun->setToolTip(Tr::tr("Stop Test Run")); m_stopTestRun->setEnabled(false); connect(m_stopTestRun, &QToolButton::clicked, TestRunner::instance(), &TestRunner::requestStopTestRun); m_filterButton = new QToolButton(m_treeView); m_filterButton->setIcon(Utils::Icons::FILTER.icon()); m_filterButton->setToolTip(Tr::tr("Filter Test Results")); m_filterButton->setProperty(StyleHelper::C_NO_ARROW, true); m_filterButton->setPopupMode(QToolButton::InstantPopup); m_filterMenu = new QMenu(m_filterButton); initializeFilterMenu(); connect(m_filterMenu, &QMenu::triggered, this, &TestResultsPane::filterMenuTriggered); m_filterButton->setMenu(m_filterMenu); m_outputToggleButton = new QToolButton(m_treeView); m_outputToggleButton->setIcon(Icons::TEXT_DISPLAY.icon()); m_outputToggleButton->setToolTip(Tr::tr("Switch Between Visual and Text Display")); m_outputToggleButton->setEnabled(true); connect(m_outputToggleButton, &QToolButton::clicked, this, &TestResultsPane::toggleOutputStyle); m_showDurationButton = new QToolButton(m_treeView); auto icon = Utils::Icon({{":/utils/images/stopwatch.png", Utils::Theme::IconsBaseColor}}); m_showDurationButton->setIcon(icon.icon()); m_showDurationButton->setToolTip(Tr::tr("Show Durations")); m_showDurationButton->setCheckable(true); m_showDurationButton->setChecked(true); connect(m_showDurationButton, &QToolButton::toggled, this, [this](bool checked) { if (auto trd = qobject_cast(m_treeView->itemDelegate())) { trd->setShowDuration(checked); if (m_model->rowCount()) { m_model->rootItem()->forAllChildren([this](TestResultItem *it) { const QModelIndex idx = m_model->indexForItem(it); emit m_model->dataChanged(idx, idx, {Qt::DisplayRole}); }); } } }); } static TestResultsPane *s_instance = nullptr; TestResultsPane *TestResultsPane::instance() { if (!s_instance) s_instance = new TestResultsPane; return s_instance; } TestResultsPane::~TestResultsPane() { delete m_treeView; if (!m_outputWidget->parent()) delete m_outputWidget; s_instance = nullptr; } void TestResultsPane::addTestResult(const TestResult &result) { const QScrollBar *scrollBar = m_treeView->verticalScrollBar(); m_atEnd = scrollBar ? scrollBar->value() == scrollBar->maximum() : true; m_model->addTestResult(result, m_expandCollapse->isChecked()); setIconBadgeNumber(m_model->resultTypeCount(ResultType::Fail) + m_model->resultTypeCount(ResultType::MessageFatal) + m_model->resultTypeCount(ResultType::UnexpectedPass)); flash(); navigateStateChanged(); } void TestResultsPane::addOutputLine(const QByteArray &outputLine, OutputChannel channel) { if (!QTC_GUARD(!outputLine.contains('\n'))) { for (const auto &line : outputLine.split('\n')) addOutputLine(line, channel); return; } m_textOutput->appendMessage(QString::fromUtf8(outputLine) + '\n', channel == OutputChannel::StdOut ? OutputFormat::StdOutFormat : OutputFormat::StdErrFormat); } QWidget *TestResultsPane::outputWidget(QWidget *parent) { if (m_outputWidget) { m_outputWidget->setParent(parent); } else { qDebug() << "This should not happen..."; } return m_outputWidget; } QList TestResultsPane::toolBarWidgets() const { QList result = {m_expandCollapse, m_runAll, m_runSelected, m_runFailed, m_runFile, m_stopTestRun, m_showDurationButton, m_outputToggleButton, m_filterButton}; for (QWidget *widget : IOutputPane::toolBarWidgets()) result.append(widget); return result; } void TestResultsPane::clearContents() { m_filterModel->clearTestResults(); if (auto delegate = qobject_cast(m_treeView->itemDelegate())) delegate->clearCache(); setIconBadgeNumber(0); navigateStateChanged(); m_summaryWidget->setVisible(false); m_autoScroll = testSettings().autoScroll(); connect(m_treeView->verticalScrollBar(), &QScrollBar::rangeChanged, this, &TestResultsPane::onScrollBarRangeChanged, Qt::UniqueConnection); m_textOutput->clear(); clearMarks(); } void TestResultsPane::setFocus() { } bool TestResultsPane::hasFocus() const { return m_treeView->hasFocus(); } bool TestResultsPane::canFocus() const { return true; } bool TestResultsPane::canNavigate() const { return true; } bool TestResultsPane::canNext() const { return m_filterModel->hasResults(); } bool TestResultsPane::canPrevious() const { return m_filterModel->hasResults(); } void TestResultsPane::goToNext() { if (!canNext()) return; const QModelIndex currentIndex = m_treeView->currentIndex(); QModelIndex nextCurrentIndex; if (currentIndex.isValid()) { // try to set next to first child or next sibling if (m_filterModel->rowCount(currentIndex)) { nextCurrentIndex = m_filterModel->index(0, 0, currentIndex); } else { nextCurrentIndex = currentIndex.sibling(currentIndex.row() + 1, 0); // if it had no sibling check siblings of parent (and grandparents if necessary) if (!nextCurrentIndex.isValid()) { QModelIndex parent = currentIndex.parent(); do { if (!parent.isValid()) break; nextCurrentIndex = parent.sibling(parent.row() + 1, 0); parent = parent.parent(); } while (!nextCurrentIndex.isValid()); } } } // if we have no current or could not find a next one, use the first item of the whole tree if (!nextCurrentIndex.isValid()) { TreeItem *rootItem = m_model->itemForIndex(QModelIndex()); // if the tree does not contain any item - don't do anything if (!rootItem || !rootItem->childCount()) return; nextCurrentIndex = m_filterModel->mapFromSource(m_model->indexForItem(rootItem->childAt(0))); } m_treeView->setCurrentIndex(nextCurrentIndex); onItemActivated(nextCurrentIndex); } void TestResultsPane::goToPrev() { if (!canPrevious()) return; const QModelIndex currentIndex = m_treeView->currentIndex(); QModelIndex nextCurrentIndex; if (currentIndex.isValid()) { // try to set next to prior sibling or parent if (currentIndex.row() > 0) { nextCurrentIndex = currentIndex.sibling(currentIndex.row() - 1, 0); // if the sibling has children, use the last one while (int rowCount = m_filterModel->rowCount(nextCurrentIndex)) nextCurrentIndex = m_filterModel->index(rowCount - 1, 0, nextCurrentIndex); } else { nextCurrentIndex = currentIndex.parent(); } } // if we have no current or didn't find a sibling/parent use the last item of the whole tree if (!nextCurrentIndex.isValid()) { const QModelIndex rootIdx = m_filterModel->index(0, 0); // if the tree does not contain any item - don't do anything if (!rootIdx.isValid()) return; // get the last (visible) top level index nextCurrentIndex = m_filterModel->index(m_filterModel->rowCount(QModelIndex()) - 1, 0); // step through until end while (int rowCount = m_filterModel->rowCount(nextCurrentIndex)) nextCurrentIndex = m_filterModel->index(rowCount - 1, 0, nextCurrentIndex); } m_treeView->setCurrentIndex(nextCurrentIndex); onItemActivated(nextCurrentIndex); } void TestResultsPane::updateFilter() { m_textOutput->updateFilterProperties(filterText(), filterCaseSensitivity(), filterUsesRegexp(), filterIsInverted(), beforeContext(), afterContext()); } void TestResultsPane::onItemActivated(const QModelIndex &index) { if (!index.isValid()) return; const TestResult testResult = m_filterModel->testResult(index); if (testResult.isValid() && !testResult.fileName().isEmpty()) EditorManager::openEditorAt(Link{testResult.fileName(), testResult.line(), 0}); } void TestResultsPane::initializeFilterMenu() { QMap textAndType; textAndType.insert(ResultType::Pass, Tr::tr("Pass")); textAndType.insert(ResultType::Fail, Tr::tr("Fail")); textAndType.insert(ResultType::ExpectedFail, Tr::tr("Expected Fail")); textAndType.insert(ResultType::UnexpectedPass, Tr::tr("Unexpected Pass")); textAndType.insert(ResultType::Skip, Tr::tr("Skip")); textAndType.insert(ResultType::Benchmark, Tr::tr("Benchmarks")); textAndType.insert(ResultType::MessageDebug, Tr::tr("Debug Messages")); textAndType.insert(ResultType::MessageWarn, Tr::tr("Warning Messages")); textAndType.insert(ResultType::MessageInternal, Tr::tr("Internal Messages")); const QSet enabled = m_filterModel->enabledFilters(); for (auto it = textAndType.cbegin(); it != textAndType.cend(); ++it) { const ResultType &result = it.key(); QAction *action = new QAction(m_filterMenu); action->setText(it.value()); action->setCheckable(true); action->setChecked(enabled.contains(result)); action->setData(int(result)); m_filterMenu->addAction(action); } m_filterMenu->addSeparator(); QAction *action = new QAction(Tr::tr("Check All Filters"), m_filterMenu); m_filterMenu->addAction(action); connect(action, &QAction::triggered, this, [this] { TestResultsPane::checkAllFilter(true); }); action = new QAction(Tr::tr("Uncheck All Filters"), m_filterMenu); m_filterMenu->addAction(action); connect(action, &QAction::triggered, this, [this] { TestResultsPane::checkAllFilter(false); }); } void TestResultsPane::updateSummaryLabel() { QString labelText = QString("

"); labelText.append(Tr::tr("Test summary")); labelText.append(":   "); int count = m_model->resultTypeCount(ResultType::Pass); labelText += QString::number(count) + ' ' + Tr::tr("passes"); count = m_model->resultTypeCount(ResultType::Fail); labelText += ", " + QString::number(count) + ' ' + Tr::tr("fails"); count = m_model->resultTypeCount(ResultType::UnexpectedPass); if (count) labelText += ", " + QString::number(count) + ' ' + Tr::tr("unexpected passes"); count = m_model->resultTypeCount(ResultType::ExpectedFail); if (count) labelText += ", " + QString::number(count) + ' ' + Tr::tr("expected fails"); count = m_model->resultTypeCount(ResultType::MessageFatal); if (count) labelText += ", " + QString::number(count) + ' ' + Tr::tr("fatals"); count = m_model->resultTypeCount(ResultType::BlacklistedFail) + m_model->resultTypeCount(ResultType::BlacklistedXFail) + m_model->resultTypeCount(ResultType::BlacklistedPass) + m_model->resultTypeCount(ResultType::BlacklistedXPass); if (count) labelText += ", " + QString::number(count) + ' ' + Tr::tr("blacklisted"); count = m_model->resultTypeCount(ResultType::Skip); if (count) labelText += ", " + QString::number(count) + ' ' + Tr::tr("skipped"); count = m_model->disabledTests(); if (count) labelText += ", " + QString::number(count) + ' ' + Tr::tr("disabled"); if (auto millisec = m_model->reportedDuration()) labelText += ".   (" + QString::number(*millisec) + " ms)

"; else labelText.append(".

"); m_summaryLabel->setText(labelText); } void TestResultsPane::checkAllFilter(bool checked) { for (QAction *action : m_filterMenu->actions()) { if (action->isCheckable()) action->setChecked(checked); } m_filterModel->enableAllResultTypes(checked); } void TestResultsPane::filterMenuTriggered(QAction *action) { m_filterModel->toggleTestResultType(TestResult::toResultType(action->data().value())); navigateStateChanged(); } bool TestResultsPane::eventFilter(QObject *object, QEvent *event) { QTC_ASSERT(m_outputWidget, return false); if (event->type() == QEvent::Resize && object->parent() == m_outputWidget) static_cast(m_treeView->itemDelegate())->clearCache(); return false; } void TestResultsPane::onTestRunStarted() { m_testRunning = true; m_stopTestRun->setEnabled(true); updateMenuItemsEnabledState(); m_summaryWidget->setVisible(false); } static bool hasFailedTests(const TestResultModel *model) { return (model->resultTypeCount(ResultType::Fail) > 0 || model->resultTypeCount(ResultType::MessageFatal) > 0 || model->resultTypeCount(ResultType::UnexpectedPass) > 0); } void TestResultsPane::onTestRunFinished() { m_testRunning = false; m_stopTestRun->setEnabled(false); updateMenuItemsEnabledState(); updateSummaryLabel(); m_summaryWidget->setVisible(true); m_model->removeCurrentTestMessage(); disconnect(m_treeView->verticalScrollBar(), &QScrollBar::rangeChanged, this, &TestResultsPane::onScrollBarRangeChanged); if (testSettings().popupOnFinish() && (!testSettings().popupOnFail() || hasFailedTests(m_model))) { popup(IOutputPane::NoModeSwitch); } createMarks(); } void TestResultsPane::onScrollBarRangeChanged(int, int max) { if (m_autoScroll && m_atEnd) m_treeView->verticalScrollBar()->setValue(max); } void TestResultsPane::onCustomContextMenuRequested(const QPoint &pos) { const bool resultsAvailable = m_filterModel->hasResults(); const bool enabled = !m_testRunning && resultsAvailable; const TestResult clicked = getTestResult(m_treeView->indexAt(pos)); QMenu menu; QAction *action = new QAction(Tr::tr("Copy"), &menu); action->setShortcut(QKeySequence(QKeySequence::Copy)); action->setEnabled(resultsAvailable && clicked.isValid()); connect(action, &QAction::triggered, this, [this, &clicked] { onCopyItemTriggered(clicked); }); menu.addAction(action); action = new QAction(Tr::tr("Copy All"), &menu); action->setEnabled(enabled); connect(action, &QAction::triggered, this, &TestResultsPane::onCopyWholeTriggered); menu.addAction(action); action = new QAction(Tr::tr("Save Output to File..."), &menu); action->setEnabled(enabled); connect(action, &QAction::triggered, this, &TestResultsPane::onSaveWholeTriggered); menu.addAction(action); const auto correlatingItem = (enabled && clicked.isValid()) ? clicked.findTestTreeItem() : nullptr; action = new QAction(Tr::tr("Run This Test"), &menu); action->setEnabled(correlatingItem && correlatingItem->canProvideTestConfiguration()); connect(action, &QAction::triggered, this, [this, &clicked] { onRunThisTestTriggered(TestRunMode::Run, clicked); }); menu.addAction(action); action = new QAction(Tr::tr("Run This Test Without Deployment"), &menu); action->setEnabled(correlatingItem && correlatingItem->canProvideTestConfiguration()); connect(action, &QAction::triggered, this, [this, &clicked] { onRunThisTestTriggered(TestRunMode::RunWithoutDeploy, clicked); }); menu.addAction(action); action = new QAction(Tr::tr("Debug This Test"), &menu); bool debugEnabled = false; if (correlatingItem) { if (correlatingItem->testBase()->type() == ITestBase::Framework) { auto testTreeItem = static_cast(correlatingItem); debugEnabled = testTreeItem && testTreeItem->canProvideDebugConfiguration(); } } action->setEnabled(debugEnabled); connect(action, &QAction::triggered, this, [this, &clicked] { onRunThisTestTriggered(TestRunMode::Debug, clicked); }); menu.addAction(action); action = new QAction(Tr::tr("Debug This Test Without Deployment"), &menu); action->setEnabled(debugEnabled); connect(action, &QAction::triggered, this, [this, &clicked] { onRunThisTestTriggered(TestRunMode::DebugWithoutDeploy, clicked); }); menu.addAction(action); menu.exec(m_treeView->mapToGlobal(pos)); } TestResult TestResultsPane::getTestResult(const QModelIndex &idx) { if (!idx.isValid()) return {}; const TestResult result = m_filterModel->testResult(idx); QTC_CHECK(result.isValid()); return result; } void TestResultsPane::onCopyItemTriggered(const TestResult &result) { QTC_ASSERT(result.isValid(), return); setClipboardAndSelection(result.outputString(true)); } void TestResultsPane::onCopyWholeTriggered() { setClipboardAndSelection(getWholeOutput()); } void TestResultsPane::onSaveWholeTriggered() { const FilePath filePath = FileUtils::getSaveFilePath(Tr::tr("Save Output To")); if (filePath.isEmpty()) return; FileSaver saver(filePath, QIODevice::Text); if (!saver.write(getWholeOutput().toUtf8()) || !saver.finalize()) { QMessageBox::critical(ICore::dialogParent(), Tr::tr("Error"), Tr::tr("Failed to write \"%1\".\n\n%2").arg(filePath.toUserOutput()) .arg(saver.errorString())); } } void TestResultsPane::onRunThisTestTriggered(TestRunMode runMode, const TestResult &result) { QTC_ASSERT(result.isValid(), return); const ITestTreeItem *item = result.findTestTreeItem(); if (item) TestRunner::instance()->runTest(runMode, item); } void TestResultsPane::toggleOutputStyle() { const bool displayText = m_outputWidget->currentIndex() == 0; m_outputWidget->setCurrentIndex(displayText ? 1 : 0); m_outputToggleButton->setIcon(displayText ? Icons::VISUAL_DISPLAY.icon() : Icons::TEXT_DISPLAY.icon()); setFilteringEnabled(displayText); setZoomButtonsEnabled(displayText); } // helper for onCopyWholeTriggered() and onSaveWholeTriggered() QString TestResultsPane::getWholeOutput(const QModelIndex &parent) { QString output; for (int row = 0, count = m_model->rowCount(parent); row < count; ++row) { QModelIndex current = m_model->index(row, 0, parent); const TestResult result = m_model->testResult(current); QTC_ASSERT(result.isValid(), continue); if (auto item = m_model->itemForIndex(current)) output.append(item->resultString()).append('\t'); output.append(result.outputString(true)).append('\n'); output.append(getWholeOutput(current)); } return output; } void TestResultsPane::createMarks(const QModelIndex &parent) { const TestResult parentResult = m_model->testResult(parent); const ResultType parentType = parentResult.isValid() ? parentResult.result() : ResultType::Invalid; const QList interested{ResultType::Fail, ResultType::UnexpectedPass}; for (int row = 0, count = m_model->rowCount(parent); row < count; ++row) { const QModelIndex index = m_model->index(row, 0, parent); const TestResult result = m_model->testResult(index); QTC_ASSERT(result.isValid(), continue); if (m_model->hasChildren(index)) createMarks(index); bool isLocationItem = result.result() == ResultType::MessageLocation; if (interested.contains(result.result()) || (isLocationItem && interested.contains(parentType))) { TestEditorMark *mark = new TestEditorMark(index, result.fileName(), result.line()); mark->setIcon(index.data(Qt::DecorationRole).value()); mark->setColor(Theme::OutputPanes_TestFailTextColor); mark->setPriority(TextEditor::TextMark::NormalPriority); mark->setToolTip(result.description()); m_marks << mark; } } } void TestResultsPane::clearMarks() { qDeleteAll(m_marks); m_marks.clear(); } static constexpr char SV_SHOW_DURATIONS[] = "AutoTest.ShowDurations"; static constexpr char SV_MESSAGE_FILTER[] = "AutoTest.MessageFilter"; void TestResultsPane::onSessionLoaded() { const bool showDurations = SessionManager::sessionValue(SV_SHOW_DURATIONS, true).toBool(); m_showDurationButton->setChecked(showDurations); const QVariantList enabledFilters = SessionManager::sessionValue(SV_MESSAGE_FILTER).toList(); if (enabledFilters.isEmpty()) { m_filterModel->enableAllResultTypes(true); if (testSettings().omitInternalMsg()) m_filterModel->toggleTestResultType(ResultType::MessageInternal); } else { m_filterModel->setEnabledFiltersFromSetting(enabledFilters); } m_filterMenu->clear(); initializeFilterMenu(); } void TestResultsPane::onAboutToSaveSession() { SessionManager::setSessionValue(SV_SHOW_DURATIONS, m_showDurationButton->isChecked()); SessionManager::setSessionValue(SV_MESSAGE_FILTER, m_filterModel->enabledFiltersAsSetting()); } void TestResultsPane::showTestResult(const QModelIndex &index) { QModelIndex mapped = m_filterModel->mapFromSource(index); if (mapped.isValid()) { popup(IOutputPane::NoModeSwitch); m_treeView->setCurrentIndex(mapped); } } } // namespace Autotest::Internal