diff options
Diffstat (limited to 'src/plugins/autotest')
106 files changed, 3945 insertions, 904 deletions
diff --git a/src/plugins/autotest/CMakeLists.txt b/src/plugins/autotest/CMakeLists.txt new file mode 100644 index 0000000000..a5291ce315 --- /dev/null +++ b/src/plugins/autotest/CMakeLists.txt @@ -0,0 +1,80 @@ +if (WITH_TESTS) + set(TEST_COMPONENT QmakeProjectManager QtSupport) +endif() + +add_qtc_plugin(AutoTest + PLUGIN_DEPENDS Core CppTools Debugger ProjectExplorer QmlJSTools TextEditor ${TEST_COMPONENT} + SOURCES + autotest.qrc + autotest_global.h + autotestconstants.h + autotesticons.h + autotestplugin.cpp autotestplugin.h + autotestunittests.qrc + boost/boostcodeparser.cpp boost/boostcodeparser.h + boost/boosttestconfiguration.cpp boost/boosttestconfiguration.h + boost/boosttestconstants.h + boost/boosttestframework.cpp boost/boosttestframework.h + boost/boosttestoutputreader.cpp boost/boosttestoutputreader.h + boost/boosttestparser.cpp boost/boosttestparser.h + boost/boosttestresult.cpp boost/boosttestresult.h + boost/boosttestsettings.cpp boost/boosttestsettings.h + boost/boosttestsettingspage.cpp boost/boosttestsettingspage.h boost/boosttestsettingspage.ui + boost/boosttesttreeitem.cpp boost/boosttesttreeitem.h + gtest/gtest_utils.cpp gtest/gtest_utils.h + gtest/gtestconfiguration.cpp gtest/gtestconfiguration.h + gtest/gtestconstants.h + gtest/gtestframework.cpp gtest/gtestframework.h + gtest/gtestoutputreader.cpp gtest/gtestoutputreader.h + gtest/gtestparser.cpp gtest/gtestparser.h + gtest/gtestresult.cpp gtest/gtestresult.h + gtest/gtestsettings.cpp gtest/gtestsettings.h + gtest/gtestsettingspage.cpp gtest/gtestsettingspage.h gtest/gtestsettingspage.ui + gtest/gtesttreeitem.cpp gtest/gtesttreeitem.h + gtest/gtestvisitors.cpp gtest/gtestvisitors.h + iframeworksettings.h + itestframework.h + itestparser.cpp itestparser.h + itestsettingspage.h + qtest/qttest_utils.cpp qtest/qttest_utils.h + qtest/qttestconfiguration.cpp qtest/qttestconfiguration.h + qtest/qttestconstants.h + qtest/qttestframework.cpp qtest/qttestframework.h + qtest/qttestoutputreader.cpp qtest/qttestoutputreader.h + qtest/qttestparser.cpp qtest/qttestparser.h + qtest/qttestresult.cpp qtest/qttestresult.h + qtest/qttestsettings.cpp qtest/qttestsettings.h + qtest/qttestsettingspage.cpp qtest/qttestsettingspage.h qtest/qttestsettingspage.ui + qtest/qttesttreeitem.cpp qtest/qttesttreeitem.h + qtest/qttestvisitors.cpp qtest/qttestvisitors.h + quick/quicktest_utils.cpp quick/quicktest_utils.h + quick/quicktestconfiguration.cpp quick/quicktestconfiguration.h + quick/quicktestframework.cpp quick/quicktestframework.h + quick/quicktestparser.cpp quick/quicktestparser.h + quick/quicktesttreeitem.cpp quick/quicktesttreeitem.h + quick/quicktestvisitors.cpp quick/quicktestvisitors.h + testcodeparser.cpp testcodeparser.h + testconfiguration.cpp testconfiguration.h + testeditormark.cpp testeditormark.h + testframeworkmanager.cpp testframeworkmanager.h + testnavigationwidget.cpp testnavigationwidget.h + testoutputreader.cpp testoutputreader.h + testresult.cpp testresult.h + testresultdelegate.cpp testresultdelegate.h + testresultmodel.cpp testresultmodel.h + testresultspane.cpp testresultspane.h + testrunconfiguration.h + testrunner.cpp testrunner.h + testsettings.cpp testsettings.h + testsettingspage.cpp testsettingspage.h testsettingspage.ui + testtreeitem.cpp testtreeitem.h + testtreeitemdelegate.cpp testtreeitemdelegate.h + testtreemodel.cpp testtreemodel.h + testtreeview.cpp testtreeview.h + EXPLICIT_MOC boost/boosttestsettingspage.h +) + +extend_qtc_plugin(AutoTest + CONDITION WITH_TESTS + SOURCES autotestunittests.cpp autotestunittests.h +) diff --git a/src/plugins/autotest/autotest.pro b/src/plugins/autotest/autotest.pro index d8af2b75c7..22e2b594c9 100644 --- a/src/plugins/autotest/autotest.pro +++ b/src/plugins/autotest/autotest.pro @@ -49,6 +49,15 @@ SOURCES += \ quick/quicktestvisitors.cpp \ quick/quicktestframework.cpp \ quick/quicktest_utils.cpp \ + boost/boostcodeparser.cpp \ + boost/boosttestframework.cpp \ + boost/boosttesttreeitem.cpp \ + boost/boosttestparser.cpp \ + boost/boosttestconfiguration.cpp \ + boost/boosttestoutputreader.cpp \ + boost/boosttestresult.cpp \ + boost/boosttestsettings.cpp \ + boost/boosttestsettingspage.cpp \ testframeworkmanager.cpp \ testeditormark.cpp @@ -104,6 +113,16 @@ HEADERS += \ quick/quicktest_utils.h \ quick/quicktestvisitors.h \ quick/quicktestframework.h \ + boost/boostcodeparser.h \ + boost/boosttestframework.h \ + boost/boosttestconstants.h \ + boost/boosttesttreeitem.h \ + boost/boosttestparser.h \ + boost/boosttestconfiguration.h \ + boost/boosttestoutputreader.h \ + boost/boosttestresult.h \ + boost/boosttestsettingspage.h \ + boost/boosttestsettings.h \ testframeworkmanager.h \ testrunconfiguration.h \ itestsettingspage.h \ @@ -114,6 +133,7 @@ RESOURCES += \ FORMS += \ testsettingspage.ui \ + boost/boosttestsettingspage.ui \ qtest/qttestsettingspage.ui \ gtest/gtestsettingspage.ui diff --git a/src/plugins/autotest/autotest.qbs b/src/plugins/autotest/autotest.qbs index b25a27ce9c..9d31d53646 100644 --- a/src/plugins/autotest/autotest.qbs +++ b/src/plugins/autotest/autotest.qbs @@ -102,6 +102,13 @@ QtcPlugin { } Group { + name: "Boost Test framework files" + files: [ + "boost/*" + ] + } + + Group { name: "Test sources" condition: qtc.testsEnabled files: [ diff --git a/src/plugins/autotest/autotest.qrc b/src/plugins/autotest/autotest.qrc index 0c3a24d7ac..51d19022da 100644 --- a/src/plugins/autotest/autotest.qrc +++ b/src/plugins/autotest/autotest.qrc @@ -2,8 +2,6 @@ <qresource prefix="/autotest"> <file>images/settingscategory_autotest.png</file> <file>images/settingscategory_autotest@2x.png</file> - <file>images/sort.png</file> - <file>images/sort@2x.png</file> <file>images/leafsort.png</file> <file>images/leafsort@2x.png</file> <file>images/benchmark.png</file> @@ -13,11 +11,14 @@ <file>images/runselected_tickmarks.png</file> <file>images/runselected_tickmarks@2x.png</file> <file>images/data.png</file> + <file>images/data@2x.png</file> <file>images/text.png</file> <file>images/text@2x.png</file> <file>images/visual.png</file> <file>images/visual@2x.png</file> <file>images/run_file.png</file> <file>images/run_file@2x.png</file> + <file>images/suite.png</file> + <file>images/suite@2x.png</file> </qresource> </RCC> diff --git a/src/plugins/autotest/autotesticons.h b/src/plugins/autotest/autotesticons.h index 83bffffeb0..c8bfb63bbf 100644 --- a/src/plugins/autotest/autotesticons.h +++ b/src/plugins/autotest/autotesticons.h @@ -30,8 +30,6 @@ namespace Autotest { namespace Icons { -const Utils::Icon SORT_ALPHABETICALLY({ - {":/autotest/images/sort.png", Utils::Theme::IconsBaseColor}}); const Utils::Icon SORT_NATURALLY({ {":/autotest/images/leafsort.png", Utils::Theme::IconsBaseColor}}); const Utils::Icon RUN_SELECTED_OVERLAY({ diff --git a/src/plugins/autotest/autotestplugin.cpp b/src/plugins/autotest/autotestplugin.cpp index 9a513bd2d4..5f90f33aca 100644 --- a/src/plugins/autotest/autotestplugin.cpp +++ b/src/plugins/autotest/autotestplugin.cpp @@ -40,6 +40,7 @@ #include "qtest/qttestframework.h" #include "quick/quicktestframework.h" #include "gtest/gtestframework.h" +#include "boost/boosttestframework.h" #include <coreplugin/icore.h> #include <coreplugin/icontext.h> @@ -176,6 +177,7 @@ bool AutotestPlugin::initialize(const QStringList &arguments, QString *errorStri m_frameworkManager->registerTestFramework(new QtTestFramework); m_frameworkManager->registerTestFramework(new QuickTestFramework); m_frameworkManager->registerTestFramework(new GTestFramework); + m_frameworkManager->registerTestFramework(new BoostTestFramework); m_frameworkManager->synchronizeSettings(ICore::settings()); m_testSettingPage = new TestSettingsPage(m_settings); @@ -248,7 +250,7 @@ void AutotestPlugin::onRunFileTriggered() if (!document) return; - const Utils::FileName &fileName = document->filePath(); + const Utils::FilePath &fileName = document->filePath(); if (fileName.isEmpty()) return; @@ -349,9 +351,15 @@ void AutotestPlugin::clearChoiceCache() s_instance->m_runconfigCache.clear(); } -QList<QObject *> AutotestPlugin::createTestObjects() const +void AutotestPlugin::popupResultsPane() { - QList<QObject *> tests; + if (s_instance) + s_instance->m_resultsPane->popup(Core::IOutputPane::NoModeSwitch); +} + +QVector<QObject *> AutotestPlugin::createTestObjects() const +{ + QVector<QObject *> tests; #ifdef WITH_TESTS tests << new AutoTestUnitTests(TestTreeModel::instance()); #endif diff --git a/src/plugins/autotest/autotestplugin.h b/src/plugins/autotest/autotestplugin.h index dcbdb5ddde..c846d3e0b0 100644 --- a/src/plugins/autotest/autotestplugin.h +++ b/src/plugins/autotest/autotestplugin.h @@ -71,6 +71,7 @@ public: static void cacheRunConfigChoice(const QString &buildTargetKey, const ChoicePair &choice); static ChoicePair cachedChoiceFor(const QString &buildTargetKey); static void clearChoiceCache(); + static void popupResultsPane(); private: void initializeMenuEntries(); @@ -78,7 +79,7 @@ private: void onRunSelectedTriggered(); void onRunFileTriggered(); void onRunUnderCursorTriggered(TestRunMode mode); - QList<QObject *> createTestObjects() const override; + QVector<QObject *> createTestObjects() const override; const QSharedPointer<TestSettings> m_settings; TestFrameworkManager *m_frameworkManager = nullptr; TestSettingsPage *m_testSettingPage = nullptr; diff --git a/src/plugins/autotest/autotestunittests.cpp b/src/plugins/autotest/autotestunittests.cpp index 706824566c..614a61a6c2 100644 --- a/src/plugins/autotest/autotestunittests.cpp +++ b/src/plugins/autotest/autotestunittests.cpp @@ -39,6 +39,7 @@ #include <projectexplorer/projectexplorer.h> #include <projectexplorer/toolchain.h> +#include <QFileInfo> #include <QSignalSpy> #include <QTest> @@ -53,9 +54,7 @@ namespace Internal { AutoTestUnitTests::AutoTestUnitTests(TestTreeModel *model, QObject *parent) : QObject(parent), - m_model(model), - m_tmpDir(nullptr), - m_isQt4(false) + m_model(model) { } @@ -64,16 +63,27 @@ void AutoTestUnitTests::initTestCase() const QList<Kit *> allKits = KitManager::kits(); if (allKits.count() != 1) QSKIP("This test requires exactly one kit to be present"); - if (auto qtVersion = QtSupport::QtKitInformation::qtVersion(allKits.first())) + if (auto qtVersion = QtSupport::QtKitAspect::qtVersion(allKits.first())) m_isQt4 = qtVersion->qtVersionString().startsWith('4'); else QSKIP("Could not figure out which Qt version is used for default kit."); - const ToolChain * const toolchain = ToolChainKitInformation::toolChain(allKits.first(), + const ToolChain * const toolchain = ToolChainKitAspect::toolChain(allKits.first(), ProjectExplorer::Constants::CXX_LANGUAGE_ID); if (!toolchain) QSKIP("This test requires that there is a kit with a toolchain."); m_tmpDir = new CppTools::Tests::TemporaryCopiedDir(":/unit_test"); + + if (!qgetenv("BOOST_INCLUDE_DIR").isEmpty()) { + m_checkBoost = true; + } else { + if (Utils::HostOsInfo::isLinuxHost() + && (QFileInfo::exists("/usr/include/boost/version.hpp") + || QFileInfo::exists("/usr/local/include/boost/version.hpp"))) { + qDebug() << "Found boost at system level - will run boost parser test."; + m_checkBoost = true; + } + } } void AutoTestUnitTests::cleanupTestCase() @@ -218,6 +228,7 @@ void AutoTestUnitTests::testCodeParserGTest() QCOMPARE(m_model->namedQuickTestsCount(), 0); QCOMPARE(m_model->unnamedQuickTestsCount(), 0); QCOMPARE(m_model->dataTagsCount(), 0); + QCOMPARE(m_model->boostTestNamesCount(), 0); } void AutoTestUnitTests::testCodeParserGTest_data() @@ -229,5 +240,51 @@ void AutoTestUnitTests::testCodeParserGTest_data() << QString(m_tmpDir->path() + "/simple_gt/simple_gt.qbs"); } +void AutoTestUnitTests::testCodeParserBoostTest() +{ + if (!m_checkBoost) + QSKIP("This test needs boost - set BOOST_INCLUDE_DIR (or have it installed)"); + + QFETCH(QString, projectFilePath); + CppTools::Tests::ProjectOpenerAndCloser projectManager; + CppTools::ProjectInfo projectInfo = projectManager.open(projectFilePath, true); + QVERIFY(projectInfo.isValid()); + + QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished())); + QSignalSpy modelUpdateSpy(m_model, SIGNAL(sweepingDone())); + QVERIFY(parserSpy.wait(20000)); + QVERIFY(modelUpdateSpy.wait()); + + QCOMPARE(m_model->boostTestNamesCount(), 5); + + QMultiMap<QString, int> expectedSuitesAndTests; + expectedSuitesAndTests.insert(QStringLiteral("Master Test Suite"), 2); // decorators w/o suite + expectedSuitesAndTests.insert(QStringLiteral("Master Test Suite"), 2); // fixtures + expectedSuitesAndTests.insert(QStringLiteral("Master Test Suite"), 3); // functions + expectedSuitesAndTests.insert(QStringLiteral("Suite1"), 4); + expectedSuitesAndTests.insert(QStringLiteral("SuiteOuter"), 5); // 2 sub suites + 3 tests + + QMultiMap<QString, int> foundNamesAndSets = m_model->boostTestSuitesAndTests(); + QCOMPARE(expectedSuitesAndTests.size(), foundNamesAndSets.size()); + for (const QString &name : expectedSuitesAndTests.keys()) + QCOMPARE(expectedSuitesAndTests.values(name), foundNamesAndSets.values(name)); + + // check also that no Qt related tests have been found + QCOMPARE(m_model->autoTestsCount(), 0); + QCOMPARE(m_model->namedQuickTestsCount(), 0); + QCOMPARE(m_model->unnamedQuickTestsCount(), 0); + QCOMPARE(m_model->dataTagsCount(), 0); + QCOMPARE(m_model->gtestNamesCount(), 0); +} + +void AutoTestUnitTests::testCodeParserBoostTest_data() +{ + QTest::addColumn<QString>("projectFilePath"); + QTest::newRow("simpleBoostTest") + << QString(m_tmpDir->path() + "/simple_boost/simple_boost.pro"); + QTest::newRow("simpleBoostTestQbs") + << QString(m_tmpDir->path() + "/simple_boost/simple_boost.qbs"); +} + } // namespace Internal } // namespace Autotest diff --git a/src/plugins/autotest/autotestunittests.h b/src/plugins/autotest/autotestunittests.h index 1d0e0b397b..3b464fc709 100644 --- a/src/plugins/autotest/autotestunittests.h +++ b/src/plugins/autotest/autotestunittests.h @@ -51,11 +51,14 @@ private slots: void testCodeParserSwitchStartup_data(); void testCodeParserGTest(); void testCodeParserGTest_data(); + void testCodeParserBoostTest(); + void testCodeParserBoostTest_data(); private: - TestTreeModel *m_model; - CppTools::Tests::TemporaryCopiedDir *m_tmpDir; - bool m_isQt4; + TestTreeModel *m_model = nullptr; + CppTools::Tests::TemporaryCopiedDir *m_tmpDir = nullptr; + bool m_isQt4 = false; + bool m_checkBoost = false; }; } // namespace Internal diff --git a/src/plugins/autotest/autotestunittests.qrc b/src/plugins/autotest/autotestunittests.qrc index 68285166cd..13db89f567 100644 --- a/src/plugins/autotest/autotestunittests.qrc +++ b/src/plugins/autotest/autotestunittests.qrc @@ -72,5 +72,22 @@ <file>unit_test/mixed_atp/tests/auto/quickauto3/quickauto3.qbs</file> <file>unit_test/mixed_atp/tests/auto/quickauto3/tst_test1.qml</file> <file>unit_test/mixed_atp/tests/auto/quickauto3/tst_test2.qml</file> + <file>unit_test/simple_boost/simple_boost.pro</file> + <file>unit_test/simple_boost/simple_boost.qbs</file> + <file>unit_test/simple_boost/src/main.cpp</file> + <file>unit_test/simple_boost/src/src.pro</file> + <file>unit_test/simple_boost/src/src.qbs</file> + <file>unit_test/simple_boost/tests/tests.pro</file> + <file>unit_test/simple_boost/tests/tests.qbs</file> + <file>unit_test/simple_boost/tests/deco/deco.pro</file> + <file>unit_test/simple_boost/tests/deco/deco.qbs</file> + <file>unit_test/simple_boost/tests/deco/enab.h</file> + <file>unit_test/simple_boost/tests/deco/main.cpp</file> + <file>unit_test/simple_boost/tests/fix/fix.cpp</file> + <file>unit_test/simple_boost/tests/fix/fix.pro</file> + <file>unit_test/simple_boost/tests/fix/fix.qbs</file> + <file>unit_test/simple_boost/tests/params/main.cpp</file> + <file>unit_test/simple_boost/tests/params/params.pro</file> + <file>unit_test/simple_boost/tests/params/params.qbs</file> </qresource> </RCC> diff --git a/src/plugins/autotest/boost/boostcodeparser.cpp b/src/plugins/autotest/boost/boostcodeparser.cpp new file mode 100644 index 0000000000..79c612285b --- /dev/null +++ b/src/plugins/autotest/boost/boostcodeparser.cpp @@ -0,0 +1,422 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "boostcodeparser.h" +#include "boosttestconstants.h" + +#include <cplusplus/Overview.h> +#include <cplusplus/Token.h> +#include <utils/qtcassert.h> + +namespace Autotest { +namespace Internal { + +using namespace CPlusPlus; + +BoostCodeParser::BoostCodeParser(const QByteArray &source, const LanguageFeatures &features, + const Document::Ptr &doc, const Snapshot &snapshot) + : m_source(source) + , m_features(features) + , m_doc(doc) + , m_snapshot(snapshot) + , m_lookupContext(m_doc, m_snapshot) +{ + m_typeOfExpression.init(m_doc, m_snapshot); +} + +static BoostTestCodeLocationAndType locationAndTypeFromToken(const Token &tkn, + const QByteArray &src, + BoostTestTreeItem::TestStates state, + const BoostTestInfoList &suitesStates) +{ + BoostTestCodeLocationAndType locationAndType; + locationAndType.m_name = QString::fromUtf8(src.mid(int(tkn.bytesBegin()), int(tkn.bytes()))); + locationAndType.m_type = TestTreeItem::TestCase; + locationAndType.m_line = tkn.lineno; + locationAndType.m_column = 0; + locationAndType.m_state = state; + if (suitesStates.isEmpty()) { + // should we handle renaming of master suite? + BoostTestInfo dummy{QString(BoostTest::Constants::BOOST_MASTER_SUITE), + BoostTestTreeItem::Enabled, 0}; + locationAndType.m_suitesState.append(dummy); + } else { + locationAndType.m_suitesState.append(suitesStates); + } + return locationAndType; +} + +static Tokens tokensForSource(const QByteArray &source, const LanguageFeatures &features) +{ + CPlusPlus::SimpleLexer lexer; + lexer.setPreprocessorMode(false); // or true? does not make a difference so far.. + lexer.setLanguageFeatures(features); + return lexer(QString::fromUtf8(source)); +} + +BoostTestCodeLocationList BoostCodeParser::findTests() +{ + m_tokens = tokensForSource(m_source, m_features); + m_currentIndex = 0; + for ( ; m_currentIndex < m_tokens.size(); ++m_currentIndex) { + const Token &token = m_tokens.at(m_currentIndex); + + if (token.kind() == T_IDENTIFIER) + handleIdentifier(); + + } + return m_testCases; +} + +void BoostCodeParser::handleIdentifier() +{ + QTC_ASSERT(m_currentIndex < m_tokens.size(), return); + const Token &token = m_tokens.at(m_currentIndex); + const QByteArray &identifier = m_source.mid(int(token.bytesBegin()), int(token.bytes())); + + if (identifier == "BOOST_AUTO_TEST_SUITE") { + handleSuiteBegin(false); + } else if (identifier == "BOOST_FIXTURE_TEST_SUITE") { + handleSuiteBegin(true); + } else if (identifier == "BOOST_AUTO_TEST_SUITE_END") { + handleSuiteEnd(); + } else if (identifier == "BOOST_TEST_CASE") { + handleTestCase(TestCaseType::Functions); + } else if (identifier == "BOOST_PARAM_TEST_CASE") { + handleTestCase(TestCaseType::Parameter); + } else if (identifier == "BOOST_AUTO_TEST_CASE") { + handleTestCase(TestCaseType::Auto); + } else if (identifier == "BOOST_FIXTURE_TEST_CASE") { + handleTestCase(TestCaseType::Fixture); + } else if (identifier == "BOOST_DATA_TEST_CASE") { + handleTestCase(TestCaseType::Data); + } else if (identifier == "BOOST_DATA_TEST_CASE_F") { + m_currentState.setFlag(BoostTestTreeItem::Fixture); + handleTestCase(TestCaseType::Data); + } else if (identifier == "BOOST_AUTO_TEST_CASE_TEMPLATE") { + m_currentState.setFlag(BoostTestTreeItem::Templated); + handleTestCase(TestCaseType::Auto); + } else if (identifier == "BOOST_FIXTURE_TEST_CASE_TEMPLATE") { + m_currentState.setFlag(BoostTestTreeItem::Fixture); + m_currentState.setFlag(BoostTestTreeItem::Templated); + handleTestCase(TestCaseType::Auto); + } else if (identifier == "BOOST_TEST_DECORATOR") { + handleDecorator(); + } +} + +void BoostCodeParser::handleSuiteBegin(bool isFixture) +{ + m_currentSuite.clear(); + if (!skipCommentsUntil(T_LPAREN)) + return; + if (!skipCommentsUntil(T_IDENTIFIER)) + return; + + const Token &token = m_tokens.at(m_currentIndex); + const QByteArray &identifier = m_source.mid(int(token.bytesBegin()), int(token.bytes())); + m_lineNo = token.lineno; + m_currentSuite = QString::fromUtf8(identifier); + if (!m_suites.isEmpty()) + m_currentSuite.prepend(m_suites.last().fullName + '/'); + + if (isFixture) { // fixture suites have a (fixture) class name as 2nd parameter + m_currentState.setFlag(BoostTestTreeItem::Fixture); + if (!skipCommentsUntil(T_COMMA)) + return; + if (!skipCommentsUntil(T_IDENTIFIER)) + return; + } + + if (!skipCommentsUntil(T_COMMA)) { + if (skipCommentsUntil(T_RPAREN)) { + // we have no decorators (or we have them before this macro) + m_suites << BoostTestInfo{m_currentSuite, m_currentState, m_lineNo}; + m_currentState = BoostTestTreeItem::Enabled; + } + } else { + handleDecorators(); + m_suites << BoostTestInfo{m_currentSuite, m_currentState, m_lineNo}; + m_currentState = BoostTestTreeItem::Enabled; + } +} + +void BoostCodeParser::handleSuiteEnd() +{ + if (!skipCommentsUntil(T_LPAREN)) + return; + skipCommentsUntil(T_RPAREN); + if (m_suites.isEmpty()) + return; + m_suites.removeLast(); +} + +void BoostCodeParser::handleTestCase(TestCaseType testCaseType) +{ + if (!skipCommentsUntil(T_LPAREN)) + return; + + Token token; + BoostTestCodeLocationAndType locationAndType; + + switch (testCaseType) { + case TestCaseType::Functions: // cannot have decorators passed as parameter + case TestCaseType::Parameter: + case TestCaseType::Data: + if (testCaseType != TestCaseType::Data) { + if (!skipCommentsUntil(T_AMPER)) { + // content without the leading lparen + const QByteArray content = contentUntil(T_RPAREN).mid(1); + const QList<QByteArray> parts = content.split(','); + if (parts.size() == 0) + return; + locationAndType = locationAndTypeFromToken(token, m_source, m_currentState, m_suites); + + const QByteArray functionName = parts.first(); + if (isBoostBindCall(functionName)) + locationAndType.m_name = QString::fromUtf8(content).append(')'); + else + locationAndType.m_name = QString::fromUtf8(functionName); + m_testCases.append(locationAndType); + m_currentState = BoostTestTreeItem::Enabled; + return; + } + if (testCaseType == TestCaseType::Parameter) + m_currentState |= BoostTestTreeItem::Parameterized; + } else if (m_currentState.testFlag(BoostTestTreeItem::Fixture)) { + // ignore first parameter (fixture) and first comma + if (!skipCommentsUntil(T_IDENTIFIER)) + return; + if (!skipCommentsUntil(T_COMMA)) + return; + } + if (!skipCommentsUntil(T_IDENTIFIER)) + return; + token = m_tokens.at(m_currentIndex); + locationAndType = locationAndTypeFromToken(token, m_source, m_currentState, m_suites); + m_testCases.append(locationAndType); + m_currentState = BoostTestTreeItem::Enabled; + return; + + case TestCaseType::Auto: + case TestCaseType::Fixture: + if (!skipCommentsUntil(T_IDENTIFIER)) + return; + token = m_tokens.at(m_currentIndex); + + if (testCaseType == TestCaseType::Fixture) { // skip fixture class parameter + m_currentState |= BoostTestTreeItem::Fixture; + if (!skipCommentsUntil(T_COMMA)) + return; + if (!skipCommentsUntil(T_IDENTIFIER)) + return; + } + break; + } + + // check and handle decorators and add the found test case + if (!skipCommentsUntil(T_COMMA)) { + if (skipCommentsUntil(T_RPAREN)) { + locationAndType = locationAndTypeFromToken(token, m_source, m_currentState, m_suites); + m_testCases.append(locationAndType); + m_currentState = BoostTestTreeItem::Enabled; + } + } else { + if (!m_currentState.testFlag(BoostTestTreeItem::Templated)) + handleDecorators(); + locationAndType = locationAndTypeFromToken(token, m_source, m_currentState, m_suites); + m_testCases.append(locationAndType); + m_currentState = BoostTestTreeItem::Enabled; + } +} + +void BoostCodeParser::handleDecorator() +{ + skipCommentsUntil(T_LPAREN); + m_currentState = BoostTestTreeItem::Enabled; + handleDecorators(); +} + +void BoostCodeParser::handleDecorators() +{ + if (!skipCommentsUntil(T_STAR)) + return; + if (!skipCommentsUntil(T_IDENTIFIER)) + return; + + QByteArray decorator = contentUntil(T_LPAREN); + if (decorator.isEmpty()) + return; + + bool aliasedOrReal; + QString symbolName; + QByteArray simplifiedName; + + if (!evalCurrentDecorator(decorator, &symbolName, &simplifiedName, &aliasedOrReal)) + return; + + if (symbolName == "decorator::disabled" || (aliasedOrReal && simplifiedName == "::disabled")) { + m_currentState.setFlag(BoostTestTreeItem::Disabled); + } else if (symbolName == "decorator::enabled" + || (aliasedOrReal && simplifiedName == "::enabled")) { + m_currentState.setFlag(BoostTestTreeItem::Disabled, false); + m_currentState.setFlag(BoostTestTreeItem::ExplicitlyEnabled); + } else if (symbolName == "decorator::enable_if" + || (aliasedOrReal && simplifiedName.startsWith("::enable_if<"))) { + // figure out the passed template value + QByteArray templateType = decorator.mid(decorator.indexOf('<') + 1); + templateType.chop(templateType.size() - templateType.indexOf('>')); + + if (templateType == "true") { + m_currentState.setFlag(BoostTestTreeItem::Disabled, false); + m_currentState.setFlag(BoostTestTreeItem::ExplicitlyEnabled); + } else if (templateType == "false") { + m_currentState.setFlag(BoostTestTreeItem::Disabled); + } else { + // FIXME we have a const(expr) bool? currently not easily achievable + } + } else if (symbolName == "decorator::fixture" + || (aliasedOrReal && simplifiedName.startsWith("::fixture"))){ + m_currentState.setFlag(BoostTestTreeItem::Fixture); + } + // TODO.. depends_on, label, precondition, timeout,... + + skipCommentsUntil(T_LPAREN); + skipCommentsUntil(T_RPAREN); + + handleDecorators(); // check for more decorators +} + +bool BoostCodeParser::skipCommentsUntil(const Kind nextExpectedKind) +{ + for (int index = m_currentIndex + 1, end = m_tokens.size(); index < end; ++index) { + const Token &token = m_tokens.at(index); + if (token.isComment()) + continue; + if (token.kind() != nextExpectedKind) + break; + m_currentIndex = index; + return true; + } + return false; +} + +QByteArray BoostCodeParser::contentUntil(Kind stopKind) +{ + QByteArray result; + for (int oldIndex = m_currentIndex, end = m_tokens.size(); oldIndex < end; ++oldIndex) { + const Token &token = m_tokens.at(oldIndex); + if (token.isComment()) + continue; + if (token.kind() == stopKind) + return result; + result.append(m_source.mid(int(token.bytesBegin()), int(token.bytes()))); + } + return result; +} + +bool BoostCodeParser::isBoostBindCall(const QByteArray &function) +{ + if (!function.contains("bind")) + return false; + int index = function.indexOf('('); + if (index == -1) + return false; + QByteArray funcCall = function.left(index); + const QList<LookupItem> lookupItems = m_typeOfExpression(funcCall, m_doc->globalNamespace()); + if (lookupItems.isEmpty()) + return false; + + if (funcCall.contains("::")) { + bool aliasedOrReal = false; + aliasedOrRealNamespace(funcCall, "boost", nullptr, &aliasedOrReal); + return aliasedOrReal; + } + + Overview overview; + for (const LookupItem &item : lookupItems) { + if (Symbol *symbol = item.declaration()) { + const QString fullQualified = overview.prettyName( + m_lookupContext.fullyQualifiedName(symbol->enclosingNamespace())); + if (fullQualified == "boost") + return true; + } + } + return false; +} + +bool BoostCodeParser::aliasedOrRealNamespace(const QByteArray &symbolName, + const QString &origNamespace, + QByteArray *simplifiedName, bool *aliasedOrReal) +{ + Overview overview; + const QByteArray classOrNamespace = symbolName.left(symbolName.lastIndexOf("::")); + const QList<LookupItem> lookup = m_typeOfExpression(classOrNamespace, m_doc->globalNamespace()); + for (const LookupItem &it : lookup) { + if (auto classOrNamespaceSymbol = it.declaration()) { + const QString fullQualified = overview.prettyName( + m_lookupContext.fullyQualifiedName(classOrNamespaceSymbol)); + if (fullQualified == origNamespace) { + *aliasedOrReal = true; + if (simplifiedName) + *simplifiedName = symbolName.mid(symbolName.lastIndexOf("::")); + return true; + } + if (auto namespaceAlias = classOrNamespaceSymbol->asNamespaceAlias()) { + if (auto aliasName = namespaceAlias->namespaceName()) { + if (overview.prettyName(aliasName) == origNamespace) { + *aliasedOrReal = true; + if (simplifiedName) + *simplifiedName = symbolName.mid(symbolName.lastIndexOf("::")); + return true; + } + } + } + } + } + return true; +} + +bool BoostCodeParser::evalCurrentDecorator(const QByteArray &decorator, QString *symbolName, + QByteArray *simplifiedName, bool *aliasedOrReal) +{ + const QList<LookupItem> lookupItems = m_typeOfExpression(decorator, m_doc->globalNamespace()); + if (lookupItems.isEmpty()) + return false; + + Overview overview; + Symbol *symbol = lookupItems.first().declaration(); + if (!symbol->name()) + return false; + + *symbolName = overview.prettyName(symbol->name()); + *aliasedOrReal = false; + if (decorator.contains("::")) + return aliasedOrRealNamespace(decorator, "boost::unit_test", simplifiedName, aliasedOrReal); + return true; +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boostcodeparser.h b/src/plugins/autotest/boost/boostcodeparser.h new file mode 100644 index 0000000000..23fd4b3c03 --- /dev/null +++ b/src/plugins/autotest/boost/boostcodeparser.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "boosttesttreeitem.h" + +#include <cplusplus/CppDocument.h> +#include <cplusplus/LookupContext.h> +#include <cplusplus/SimpleLexer.h> +#include <cplusplus/TypeOfExpression.h> + +#include <QByteArray> + +namespace Autotest { +namespace Internal { + +class BoostCodeParser +{ +public: + BoostCodeParser(const QByteArray &source, const CPlusPlus::LanguageFeatures &features, + const CPlusPlus::Document::Ptr &doc, const CPlusPlus::Snapshot &snapshot); + virtual ~BoostCodeParser() = default; + BoostTestCodeLocationList findTests(); +private: + enum class TestCaseType {Auto, Functions, Parameter, Fixture, Data}; + + void handleIdentifier(); + void handleSuiteBegin(bool isFixture); + void handleSuiteEnd(); + void handleTestCase(TestCaseType testCaseType); + void handleDecorator(); + void handleDecorators(); + + bool skipCommentsUntil(CPlusPlus::Kind nextExpectedKind); // moves currentIndex if succeeds + QByteArray contentUntil(CPlusPlus::Kind stopKind); // does not move currentIndex + + bool isBoostBindCall(const QByteArray &function); + bool aliasedOrRealNamespace(const QByteArray &symbolName, const QString &origNamespace, + QByteArray *simplifiedName, bool *aliasedOrReal); + bool evalCurrentDecorator(const QByteArray &decorator, QString *symbolName, + QByteArray *simplifiedName, bool *aliasedOrReal); + + const QByteArray &m_source; + const CPlusPlus::LanguageFeatures &m_features; + const CPlusPlus::Document::Ptr &m_doc; + const CPlusPlus::Snapshot &m_snapshot; + CPlusPlus::LookupContext m_lookupContext; + CPlusPlus::TypeOfExpression m_typeOfExpression; + CPlusPlus::Tokens m_tokens; + int m_currentIndex = 0; + BoostTestCodeLocationList m_testCases; + QVector<BoostTestInfo> m_suites; + QString m_currentSuite; + BoostTestTreeItem::TestStates m_currentState = BoostTestTreeItem::Enabled; + unsigned m_lineNo = 0; +}; + +} // Internal +} // Autotest diff --git a/src/plugins/autotest/boost/boosttestconfiguration.cpp b/src/plugins/autotest/boost/boosttestconfiguration.cpp new file mode 100644 index 0000000000..276c83bc6d --- /dev/null +++ b/src/plugins/autotest/boost/boosttestconfiguration.cpp @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "boosttestconfiguration.h" +#include "boosttestconstants.h" +#include "boosttestoutputreader.h" +#include "boosttestsettings.h" + +#include "../autotestplugin.h" +#include "../testframeworkmanager.h" +#include "../testsettings.h" + +namespace Autotest { +namespace Internal { + +static QSharedPointer<BoostTestSettings> getBoostSettings() +{ + const Core::Id id = Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix( + BoostTest::Constants::FRAMEWORK_NAME); + TestFrameworkManager *manager = TestFrameworkManager::instance(); + return qSharedPointerCast<BoostTestSettings>(manager->settingsForTestFramework(id)); +} + +TestOutputReader *BoostTestConfiguration::outputReader(const QFutureInterface<TestResultPtr> &fi, + QProcess *app) const +{ + auto settings = getBoostSettings(); + return new BoostTestOutputReader(fi, app, buildDirectory(), projectFile(), + settings->logLevel, settings->reportLevel); +} + +enum class InterferingType { Options, EnvironmentVariables }; + +static QStringList interfering(InterferingType type) +{ + const QStringList knownInterfering { "log_level", "log_format", "log_sink", + "report_level", "report_format", "report_sink", + "output_format", "color_output", "no_color_output", + "catch_system_errors", "no_catch_system_errors", + "detect_fp_exceptions", "no_detect_fp_exceptions", + "detect_memory_leaks", "random", "run_test", + "show_progress", "result_code", "no_result_code", + "help", "list_content", "list_labels", "version" + }; + switch (type) { + case InterferingType::Options: + return Utils::transform(knownInterfering, [](const QString &item) { + return QString("--" + item); + }); + case InterferingType::EnvironmentVariables: + return Utils::transform(knownInterfering, [](const QString &item) { + return QString("BOOST_TEST_" + item).toUpper(); + }); + } + return QStringList(); +} + +static QStringList filterInterfering(const QStringList &provided, QStringList *omitted) +{ + const QStringList knownInterfering = interfering(InterferingType::Options); + const QString interferingSingleWithParam = "efklortcpsx?"; + QStringList allowed; + bool filterNextArg = false; + bool ignoreRest = false; + for (auto arg : provided) { + bool filterArg = filterNextArg; + filterNextArg = false; + if (ignoreRest) { + allowed.append(arg); + continue; + } + bool interferes = false; + if (filterArg && !arg.startsWith('-')) { + interferes = true; + } else if (arg.startsWith("--")) { + if (arg.size() == 2) + ignoreRest = true; + else + interferes = knownInterfering.contains(arg.left(arg.indexOf('='))); + } else if (arg.startsWith('-') && arg.size() > 1) { + interferes = interferingSingleWithParam.contains(arg.at(1)); + filterNextArg = interferes; + } + if (!interferes) + allowed.append(arg); + else if (omitted) + omitted->append(arg); + } + return allowed; +} + +QStringList BoostTestConfiguration::argumentsForTestRunner(QStringList *omitted) const +{ + auto boostSettings = getBoostSettings(); + QStringList arguments; + arguments << "-l" << BoostTestSettings::logLevelToOption(boostSettings->logLevel); + arguments << "-r" << BoostTestSettings::reportLevelToOption(boostSettings->reportLevel); + arguments << "--no_color_output"; // ensure that colored output is not used as default + + if (boostSettings->randomize) + arguments << QString("--random=").append(QString::number(boostSettings->seed)); + + if (boostSettings->systemErrors) + arguments << "-s"; + if (boostSettings->fpExceptions) + arguments << "--detect_fp_exceptions"; + if (!boostSettings->memLeaks) + arguments << "--detect_memory_leaks=0"; + + // TODO improve the test case gathering and arguments building to avoid too long command lines + for (const QString &test : testCases()) + arguments << "-t" << test; + + if (AutotestPlugin::settings()->processArgs) { + arguments << filterInterfering(runnable().commandLineArguments.split( + ' ', QString::SkipEmptyParts), omitted); + } + return arguments; +} + +Utils::Environment BoostTestConfiguration::filteredEnvironment(const Utils::Environment &original) const +{ + const QStringList interferingEnv = interfering(InterferingType::EnvironmentVariables); + + Utils::Environment result = original; + for (const QString &key : interferingEnv) + result.unset(key); + return result; +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttestconfiguration.h b/src/plugins/autotest/boost/boosttestconfiguration.h new file mode 100644 index 0000000000..e792fe21d5 --- /dev/null +++ b/src/plugins/autotest/boost/boosttestconfiguration.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "../testconfiguration.h" + +namespace Autotest { +namespace Internal { + +class BoostTestConfiguration : public DebuggableTestConfiguration +{ +public: + BoostTestConfiguration() {} + TestOutputReader *outputReader(const QFutureInterface<TestResultPtr> &fi, + QProcess *app) const override; + QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; + Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; +}; + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttestconstants.h b/src/plugins/autotest/boost/boosttestconstants.h new file mode 100644 index 0000000000..2c993c1d4f --- /dev/null +++ b/src/plugins/autotest/boost/boosttestconstants.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QtGlobal> + +namespace Autotest { +namespace BoostTest { +namespace Constants { + +const char FRAMEWORK_NAME[] = "Boost"; +const char FRAMEWORK_SETTINGS_CATEGORY[] = QT_TRANSLATE_NOOP("BoostTestFramework", "Boost Test"); +const unsigned FRAMEWORK_PRIORITY = 11; +const char BOOST_MASTER_SUITE[] = "Master Test Suite"; + +} // namespace Constants +} // namespace BoostTest +} // namespace AutoTest diff --git a/src/plugins/autotest/boost/boosttestframework.cpp b/src/plugins/autotest/boost/boosttestframework.cpp new file mode 100644 index 0000000000..a6b7515154 --- /dev/null +++ b/src/plugins/autotest/boost/boosttestframework.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "boosttestframework.h" +#include "boosttestconstants.h" +#include "boosttestsettings.h" +#include "boosttestsettingspage.h" +#include "boosttesttreeitem.h" +#include "boosttestparser.h" +#include "../testframeworkmanager.h" + +namespace Autotest { +namespace Internal { + +ITestParser *BoostTestFramework::createTestParser() const +{ + return new BoostTestParser; +} + +TestTreeItem *BoostTestFramework::createRootNode() const +{ + return new BoostTestTreeItem( + QCoreApplication::translate("BoostTestFramework", + BoostTest::Constants::FRAMEWORK_SETTINGS_CATEGORY), + QString(), TestTreeItem::Root); +} + +const char *BoostTestFramework::name() const +{ + return BoostTest::Constants::FRAMEWORK_NAME; +} + +unsigned BoostTestFramework::priority() const +{ + return BoostTest::Constants::FRAMEWORK_PRIORITY; +} + +IFrameworkSettings *BoostTestFramework::createFrameworkSettings() const +{ + return new BoostTestSettings; +} + +ITestSettingsPage *BoostTestFramework::createSettingsPage(QSharedPointer<IFrameworkSettings> settings) const +{ + return new BoostTestSettingsPage(settings, this); +} + +bool BoostTestFramework::hasFrameworkSettings() const +{ + return true; +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttestframework.h b/src/plugins/autotest/boost/boosttestframework.h new file mode 100644 index 0000000000..c9db343deb --- /dev/null +++ b/src/plugins/autotest/boost/boosttestframework.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "../itestframework.h" + +namespace Autotest { +namespace Internal { + +class BoostTestFramework : public ITestFramework +{ +public: + BoostTestFramework() : ITestFramework(true) {} + const char *name() const override; + unsigned priority() const override; + IFrameworkSettings *createFrameworkSettings() const override; + ITestSettingsPage *createSettingsPage(QSharedPointer<IFrameworkSettings> settings) const override; + bool hasFrameworkSettings() const override; +protected: + ITestParser *createTestParser() const override; + TestTreeItem *createRootNode() const override; +}; + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttestoutputreader.cpp b/src/plugins/autotest/boost/boosttestoutputreader.cpp new file mode 100644 index 0000000000..3ae5756d3a --- /dev/null +++ b/src/plugins/autotest/boost/boosttestoutputreader.cpp @@ -0,0 +1,457 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "boosttestoutputreader.h" +#include "boosttestsettings.h" +#include "boosttestresult.h" + +#include <utils/qtcassert.h> + +#include <QDir> +#include <QFileInfo> +#include <QLoggingCategory> +#include <QRegularExpression> + +namespace Autotest { +namespace Internal { + +static Q_LOGGING_CATEGORY(orLog, "qtc.autotest.boost.outputreader", QtWarningMsg) + +static QString constructSourceFilePath(const QString &path, const QString &filePath) +{ + return QFileInfo(path, filePath).canonicalFilePath(); +} + +BoostTestOutputReader::BoostTestOutputReader(const QFutureInterface<TestResultPtr> &futureInterface, + QProcess *testApplication, + const QString &buildDirectory, + const QString &projectFile, + LogLevel log, ReportLevel report) + : TestOutputReader(futureInterface, testApplication, buildDirectory) + , m_projectFile(projectFile) + , m_logLevel(log) + , m_reportLevel(report) +{ + if (m_testApplication) { + connect(m_testApplication, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), + this, &BoostTestOutputReader::onFinished); + } +} + +// content of "error:..." / "info:..." / ... messages +static QString caseFromContent(const QString &content) +{ + const int length = content.length(); + if (content.startsWith("last checkpoint:")) { + int index = content.indexOf('"'); + if (index != 17 || length <= 18) { + qCDebug(orLog) << "double quote position" << index << " or content length" << length + << "wrong on content" << content; + return QString(); + } + index = content.indexOf('"', 18); + if (index == -1) { + qCDebug(orLog) << "no closing double quote" << content; + return QString(); + } + return content.mid(18, index - 1); + } + + int index = content.indexOf(": in "); + if (index == -1) // "info: check true has passed" + return QString(); + + if (index <= 4 || length < index + 4) { + qCDebug(orLog) << "unexpected position" << index << "for info" << content; + return QString(); + } + + QString result = content.mid(index + 5); + static QRegularExpression functionName("\"(.+)\":.*"); + const QRegularExpressionMatch matcher = functionName.match(result); + if (!matcher.hasMatch()) { + qCDebug(orLog) << "got no match"; + return QString(); + } + return matcher.captured(1); +} + +void BoostTestOutputReader::sendCompleteInformation() +{ + QTC_ASSERT(m_result != ResultType::Invalid, return); + BoostTestResult *result = new BoostTestResult(id(), m_projectFile, m_currentModule); + result->setTestSuite(m_currentSuite); + result->setTestCase(m_currentTest); + if (m_lineNumber) { + result->setLine(m_lineNumber); + result->setFileName(m_fileName); + } + + result->setDescription(m_description); + result->setResult(m_result); + reportResult(TestResultPtr(result)); + m_result = ResultType::Invalid; +} + +void BoostTestOutputReader::handleMessageMatch(const QRegularExpressionMatch &match) +{ + m_fileName = constructSourceFilePath(m_buildDir, match.captured(1)); + m_lineNumber = match.captured(2).toInt(); + + const QString &content = match.captured(3); + if (content.startsWith("info:")) { + if (m_currentTest.isEmpty() || m_logLevel > LogLevel::UnitScope) { + QString tmp = caseFromContent(content); + if (!tmp.isEmpty()) + m_currentTest = tmp; + } + m_result = ResultType::Pass; + m_description = content; + } else if (content.startsWith("error:")) { + if (m_currentTest.isEmpty() || m_logLevel > LogLevel::UnitScope) + m_currentTest = caseFromContent(content); + m_result = ResultType::Fail; + if (m_reportLevel == ReportLevel::No) + ++m_summary[ResultType::Fail]; + m_description = content; + } else if (content.startsWith("fatal error:")) { + if (m_currentTest.isEmpty() || m_logLevel > LogLevel::UnitScope) + m_currentTest = caseFromContent(content); + m_result = ResultType::MessageFatal; + m_description = content; + } else if (content.startsWith("last checkpoint:")) { + if (m_currentTest.isEmpty() || m_logLevel > LogLevel::UnitScope) + m_currentTest = caseFromContent(content); + m_result = ResultType::MessageInfo; + m_description = content; + } else if (content.startsWith("Entering")) { + m_result = ResultType::TestStart; + const QString type = match.captured(8); + if (type == "case") { + m_currentTest = match.captured(9); + m_description = tr("Executing test case %1").arg(m_currentTest); + } else if (type == "suite") { + m_currentSuite = match.captured(9); + m_description = tr("Executing test suite %1").arg(m_currentSuite); + } + } else if (content.startsWith("Leaving")) { + const QString type = match.captured(10); + if (type == "case") { + if (m_currentTest != match.captured(11) && m_currentTest.isEmpty()) + m_currentTest = match.captured(11); + m_result = ResultType::TestEnd; + m_description = tr("Test execution took %1").arg(match.captured(12)); + } else if (type == "suite") { + if (m_currentSuite != match.captured(11) && m_currentSuite.isEmpty()) + m_currentSuite = match.captured(11); + m_currentTest.clear(); + m_result = ResultType::TestEnd; + m_description = tr("Test suite execution took %1").arg(match.captured(12)); + } + } else if (content.startsWith("Test case ")) { + m_currentTest = match.captured(4); + m_result = ResultType::Skip; + if (m_reportLevel == ReportLevel::Confirm || m_reportLevel == ReportLevel::No) + ++m_summary[ResultType::Skip]; + m_description = content; + } + + if (m_result != ResultType::Invalid) // we got a new result + sendCompleteInformation(); +} + +void BoostTestOutputReader::processOutputLine(const QByteArray &outputLineWithNewLine) +{ + static QRegularExpression newTestStart("^Running (\\d+) test cases?\\.\\.\\.$"); + static QRegularExpression dependency("^Including test case (.+) as a dependency of " + "test case (.+)$"); + static QRegularExpression messages("^(.+)\\((\\d+)\\): (info: (.+)|error: (.+)|" + "fatal error: (.+)|last checkpoint: (.+)" + "|Entering test (case|suite) \"(.+)\"" + "|Leaving test (case|suite) \"(.+)\"; testing time: (\\d+.+)" + "|Test case \"(.+)\" is skipped because .+$)$"); + static QRegularExpression moduleMssg("^(Entering test module \"(.+)\"|" + "Leaving test module \"(.+)\"; testing time: (\\d+.+))$"); + static QRegularExpression noAssertion("^Test case (.*) did not check any assertions$"); + + static QRegularExpression summaryPreamble("^\\s*Test (module|suite|case) \"(.*)\" has " + "(failed|passed)( with:)?$"); + static QRegularExpression summarySkip("^\\s+Test case \"(.*)\" was skipped$"); + static QRegularExpression summaryDetail("^\\s+(\\d+) test cases? out of (\\d+) " + "(failed|passed|skipped)$"); + static QRegularExpression summaryAssertion("^\\s+(\\d+) assertions? out of (\\d+) " + "(failed|passed)$"); + + static QRegularExpression finish("^\\*{3} (\\d+) failure(s are| is) detected in the " + "test module \"(.*)\"$"); + static QRegularExpression errDetect("^\\*{3} Errors where detected in the " + "test module \"(.*}\"; see standard output for details"); + QString noErrors("*** No errors detected"); + + const QString line = QString::fromUtf8(chopLineBreak(outputLineWithNewLine)); + if (line.trimmed().isEmpty()) + return; + + QRegularExpressionMatch match = messages.match(line); + if (match.hasMatch()) { + handleMessageMatch(match); + return; + } + + match = dependency.match(line); + if (match.hasMatch()) { + if (m_result != ResultType::Invalid) + sendCompleteInformation(); + BoostTestResult *result = new BoostTestResult(id(), m_projectFile, m_currentModule); + result->setDescription(match.captured(0)); + result->setResult(ResultType::MessageInfo); + reportResult(TestResultPtr(result)); + return; + } + + match = newTestStart.match(line); + if (match.hasMatch()) { + if (m_result != ResultType::Invalid) + sendCompleteInformation(); + m_testCaseCount = match.captured(1).toInt(); + m_description.clear(); + return; + } + + match = moduleMssg.match(line); + if (match.hasMatch()) { + if (m_result != ResultType::Invalid) + sendCompleteInformation(); + if (match.captured(1).startsWith("Entering")) { + m_currentModule = match.captured(2); + BoostTestResult *result = new BoostTestResult(id(), m_projectFile, m_currentModule); + result->setDescription(tr("Executing test module %1").arg(m_currentModule)); + result->setResult(ResultType::TestStart); + reportResult(TestResultPtr(result)); + m_description.clear(); + } else { + QTC_CHECK(m_currentModule == match.captured(3)); + BoostTestResult *result = new BoostTestResult(id(), m_projectFile, m_currentModule); + result->setDescription(tr("Test module execution took %1").arg(match.captured(4))); + result->setResult(ResultType::TestEnd); + reportResult(TestResultPtr(result)); + + m_currentTest.clear(); + m_currentSuite.clear(); + m_currentModule.clear(); + m_description.clear(); + } + return; + } + + match = noAssertion.match(line); + if (match.hasMatch()) { + if (m_result != ResultType::Invalid) + sendCompleteInformation(); + const QString caseWithOptionalSuite = match.captured(1); + int index = caseWithOptionalSuite.lastIndexOf('/'); + if (index == -1) { + QTC_CHECK(caseWithOptionalSuite == m_currentTest); + } else { + QTC_CHECK(caseWithOptionalSuite.mid(index + 1) == m_currentTest); + int sIndex = caseWithOptionalSuite.lastIndexOf('/', index - 1); + if (sIndex == -1) { + QTC_CHECK(caseWithOptionalSuite.left(index) == m_currentSuite); + m_currentSuite = caseWithOptionalSuite.left(index); // FIXME should not be necessary - but we currently do not care for the whole suite path + } else { + QTC_CHECK(caseWithOptionalSuite.mid(sIndex + 1, index - sIndex - 1) == m_currentSuite); + } + } + createAndReportResult(match.captured(0), ResultType::MessageWarn); + return; + } + + match = summaryPreamble.match(line); + if (match.hasMatch()) { + createAndReportResult(match.captured(0), ResultType::MessageInfo); + if (m_reportLevel == ReportLevel::Detailed || match.captured(4).isEmpty()) { + if (match.captured(1) == "case") { + if (match.captured(3) == "passed") + ++m_summary[ResultType::Pass]; + else + ++m_summary[ResultType::Fail]; + } + } + return; + } + + match = summaryDetail.match(line); + if (match.hasMatch()) { + createAndReportResult(match.captured(0), ResultType::MessageInfo); + int report = match.captured(1).toInt(); + QString type = match.captured(3); + if (m_reportLevel != ReportLevel::Detailed) { + if (type == "passed") + m_summary[ResultType::Pass] += report; + else if (type == "failed") + m_summary[ResultType::Fail] += report; + else if (type == "skipped") + m_summary[ResultType::Skip] += report; + } + + return; + } + + match = summaryAssertion.match(line); + if (match.hasMatch()) { + createAndReportResult(match.captured(0), ResultType::MessageInfo); + return; + } + + match = summarySkip.match(line); + if (match.hasMatch()) { + createAndReportResult(match.captured(0), ResultType::MessageInfo); + if (m_reportLevel == ReportLevel::Detailed) + ++m_summary[ResultType::Skip]; + return; + } + + match = finish.match(line); + if (match.hasMatch()) { + if (m_result != ResultType::Invalid) + sendCompleteInformation(); + BoostTestResult *result = new BoostTestResult(id(), m_projectFile, QString()); + int failed = match.captured(1).toInt(); + QString txt = tr("%1 failures detected in %2.").arg(failed).arg(match.captured(3)); + int passed = (m_testCaseCount != -1) + ? m_testCaseCount - failed - m_summary[ResultType::Skip] : -1; + if (m_testCaseCount != -1) + txt.append(' ').append(tr("%1 tests passed.").arg(passed)); + result->setDescription(txt); + result->setResult(ResultType::MessageInfo); + reportResult(TestResultPtr(result)); + if (m_reportLevel == ReportLevel::Confirm) { // for the final summary + m_summary[ResultType::Pass] += passed; + m_summary[ResultType::Fail] += failed; + } + m_testCaseCount = -1; + return; + } + + if (line == noErrors) { + if (m_result != ResultType::Invalid) + sendCompleteInformation(); + BoostTestResult *result = new BoostTestResult(id(), m_projectFile, QString()); + QString txt = tr("No errors detected."); + if (m_testCaseCount != -1) + txt.append(' ').append(tr("%1 tests passed.").arg(m_testCaseCount)); + result->setDescription(txt); + result->setResult(ResultType::MessageInfo); + reportResult(TestResultPtr(result)); + if (m_reportLevel == ReportLevel::Confirm) // for the final summary + m_summary.insert(ResultType::Pass, m_testCaseCount); + return; + } + + // some plain output... + if (!m_description.isEmpty()) + m_description.append('\n'); + m_description.append(line); +} + +void BoostTestOutputReader::processStdError(const QByteArray &output) +{ + // we need to process the output, Boost UTF uses both out streams + int start = 0; + int index = -1; + while ((index = output.indexOf('\n', start)) != -1) { + const QByteArray &line = output.mid(start, index - start + 1); + if (!line.isEmpty()) { + if (line != QByteArray(1, '\n')) + processOutputLine(line); + emit newOutputAvailable(line); + } + start = index + 1; + } + if (start > 0) { // remove? this never happens + const QByteArray lastLine = output.mid(start) + '\n'; + if (!lastLine.isEmpty()) { + if (lastLine != QByteArray(1, '\n')) + processOutputLine(lastLine); + emit newOutputAvailable(lastLine); + } + } +} + +TestResultPtr BoostTestOutputReader::createDefaultResult() const +{ + BoostTestResult *result = new BoostTestResult(id(), m_projectFile, m_currentModule); + result->setTestSuite(m_currentSuite); + result->setTestCase(m_currentTest); + + return TestResultPtr(result); +} + +void BoostTestOutputReader::onFinished(int exitCode, QProcess::ExitStatus /*exitState*/) { + if (m_reportLevel == ReportLevel::No && m_testCaseCount != -1) { + int reportedFailsAndSkips = m_summary[ResultType::Fail] + m_summary[ResultType::Skip]; + m_summary.insert(ResultType::Pass, m_testCaseCount - reportedFailsAndSkips); + } + // boost::exit_success (0), boost::exit_test_failure (201) + // or boost::exit_exception_failure (200) + // be graceful and do not add a fatal for exit_test_failure + if (m_logLevel == LogLevel::Nothing && m_reportLevel == ReportLevel::No) { + switch (exitCode) { + case 0: + reportNoOutputFinish(tr("Running tests exited with ") + "boost::exit_success.", + ResultType::Pass); + break; + case 200: + reportNoOutputFinish( + tr("Running tests exited with ") + "boost::exit_test_exception.", + ResultType::MessageFatal); + break; + case 201: + reportNoOutputFinish(tr("Running tests exited with ") + + "boost::exit_test_failure.", ResultType::Fail); + break; + } + } else if (exitCode != 0 && exitCode != 201 && !m_description.isEmpty()) { + if (m_description.startsWith("Test setup error:")) { + createAndReportResult(m_description + '\n' + tr("Executable: %1") + .arg(id()), ResultType::MessageWarn); + } else { + createAndReportResult(tr("Running tests failed.\n%1\nExecutable: %2") + .arg(m_description).arg(id()), ResultType::MessageFatal); + } + } +} + +void BoostTestOutputReader::reportNoOutputFinish(const QString &description, ResultType type) +{ + BoostTestResult *result = new BoostTestResult(id(), m_projectFile, m_currentModule); + result->setTestCase(tr("Running tests without output.")); + result->setDescription(description); + result->setResult(type); + reportResult(TestResultPtr(result)); +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttestoutputreader.h b/src/plugins/autotest/boost/boosttestoutputreader.h new file mode 100644 index 0000000000..e16c1fb996 --- /dev/null +++ b/src/plugins/autotest/boost/boosttestoutputreader.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "../testoutputreader.h" + +namespace Autotest { +namespace Internal { + +class BoostTestResult; +class TestTreeItem; +enum class LogLevel; +enum class ReportLevel; + +class BoostTestOutputReader : public TestOutputReader +{ +public: + BoostTestOutputReader(const QFutureInterface<TestResultPtr> &futureInterface, + QProcess *testApplication, const QString &buildDirectory, + const QString &projectFile, LogLevel log, ReportLevel report); +protected: + void processOutputLine(const QByteArray &outputLineWithNewLine) override; + void processStdError(const QByteArray &output) override; + TestResultPtr createDefaultResult() const override; + +private: + void onFinished(int exitCode, QProcess::ExitStatus /*exitState*/); + void sendCompleteInformation(); + void handleMessageMatch(const QRegularExpressionMatch &match); + void reportNoOutputFinish(const QString &description, ResultType type); + QString m_projectFile; + QString m_currentModule; + QString m_currentSuite; + QString m_currentTest; + QString m_description; + QString m_fileName; + ResultType m_result = ResultType::Invalid; + int m_lineNumber = 0; + int m_testCaseCount = -1; + LogLevel m_logLevel; + ReportLevel m_reportLevel; +}; + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttestparser.cpp b/src/plugins/autotest/boost/boosttestparser.cpp new file mode 100644 index 0000000000..6f953e515a --- /dev/null +++ b/src/plugins/autotest/boost/boosttestparser.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "boosttestparser.h" +#include "boostcodeparser.h" +#include "boosttesttreeitem.h" + +#include <cpptools/cppmodelmanager.h> + +#include <QMap> +#include <QRegularExpression> +#include <QRegularExpressionMatch> + +namespace Autotest { +namespace Internal { + +namespace BoostTestUtils { +static const QStringList relevant = { + QStringLiteral("BOOST_AUTO_TEST_CASE"), QStringLiteral("BOOST_TEST_CASE"), + QStringLiteral("BOOST_DATA_TEST_CASE"), QStringLiteral("BOOST_FIXTURE_TEST_CASE"), + QStringLiteral("BOOST_PARAM_TEST_CASE"), QStringLiteral("BOOST_DATA_TEST_CASE_F"), + QStringLiteral("BOOST_AUTO_TEST_CASE_TEMPLATE"), + QStringLiteral("BOOST_FIXTURE_TEST_CASE_TEMPLATE"), +}; + +bool isBoostTestMacro(const QString ¯o) +{ + return relevant.contains(macro); +} +} // BoostTestUtils + +TestTreeItem *BoostTestParseResult::createTestTreeItem() const +{ + if (itemType == TestTreeItem::Root) + return nullptr; + + BoostTestTreeItem *item = new BoostTestTreeItem(displayName, fileName, itemType); + item->setProFile(proFile); + item->setLine(line); + item->setColumn(column); + item->setStates(state); + item->setFullName(name); + + for (const TestParseResult *funcParseResult : children) + item->appendChild(funcParseResult->createTestTreeItem()); + return item; +} + + +static bool includesBoostTest(const CPlusPlus::Document::Ptr &doc, + const CPlusPlus::Snapshot &snapshot) +{ + static const QRegularExpression boostTestHpp("^.*/boost/test/.*\\.hpp$"); + for (const CPlusPlus::Document::Include &inc : doc->resolvedIncludes()) { + if (boostTestHpp.match(inc.resolvedFileName()).hasMatch()) + return true; + } + + for (const QString &include : snapshot.allIncludesForDocument(doc->fileName())) { + if (boostTestHpp.match(include).hasMatch()) + return true; + } + + return false; +} + +static bool hasBoostTestMacros(const CPlusPlus::Document::Ptr &doc) +{ + for (const CPlusPlus::Document::MacroUse ¯o : doc->macroUses()) { + if (!macro.isFunctionLike()) + continue; + if (BoostTestUtils::isBoostTestMacro(QLatin1String(macro.macro().name()))) + return true; + } + return false; +} + +static BoostTestParseResult *createParseResult(const QString &name, const QString &filePath, + const QString &projectFile, const Core::Id &id, + TestTreeItem::Type type, const BoostTestInfo &info) +{ + BoostTestParseResult *partialSuite = new BoostTestParseResult(id); + partialSuite->itemType = type; + partialSuite->fileName = filePath; + partialSuite->name = info.fullName; + partialSuite->displayName = name; + partialSuite->line = info.line; + partialSuite->column = 0; + partialSuite->proFile = projectFile; + partialSuite->state = info.state; + return partialSuite; + +} + +static bool handleBoostTest(QFutureInterface<TestParseResultPtr> futureInterface, + const CPlusPlus::Document::Ptr &doc, + const CPlusPlus::Snapshot &snapshot, + const Core::Id &id) +{ + const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); + const QString &filePath = doc->fileName(); + + const QList<CppTools::ProjectPart::Ptr> projectParts = modelManager->projectPart(filePath); + if (projectParts.isEmpty()) // happens if shutting down while parsing + return false; + const CppTools::ProjectPart::Ptr projectPart = projectParts.first(); + const auto projectFile = projectPart->projectFile; + const QByteArray &fileContent = CppParser::getFileContent(filePath); + + BoostCodeParser codeParser(fileContent, projectPart->languageFeatures, doc, snapshot); + const BoostTestCodeLocationList foundTests = codeParser.findTests(); + if (foundTests.isEmpty()) + return false; + + for (const BoostTestCodeLocationAndType &locationAndType : foundTests) { + BoostTestInfoList suitesStates = locationAndType.m_suitesState; + BoostTestInfo firstSuite = suitesStates.first(); + QStringList suites = firstSuite.fullName.split('/'); + BoostTestParseResult *topLevelSuite = createParseResult(suites.first(), filePath, + projectFile, id, + TestTreeItem::TestSuite, + firstSuite); + BoostTestParseResult *currentSuite = topLevelSuite; + suitesStates.removeFirst(); + while (suitesStates.size()) { + firstSuite = suitesStates.first(); + suites = firstSuite.fullName.split('/'); + BoostTestParseResult *suiteResult = createParseResult(suites.last(), filePath, + projectFile, id, + TestTreeItem::TestSuite, + firstSuite); + currentSuite->children.append(suiteResult); + suitesStates.removeFirst(); + currentSuite = suiteResult; + } + + if (currentSuite) { + BoostTestInfo tmpInfo{ + locationAndType.m_suitesState.last().fullName + "::" + locationAndType.m_name, + locationAndType.m_state, locationAndType.m_line}; + BoostTestParseResult *funcResult = createParseResult(locationAndType.m_name, filePath, + projectFile, id, + locationAndType.m_type, + tmpInfo); + currentSuite->children.append(funcResult); + futureInterface.reportResult(TestParseResultPtr(topLevelSuite)); + } + } + return true; +} + +bool BoostTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface, + const QString &fileName) +{ + CPlusPlus::Document::Ptr doc = document(fileName); + if (doc.isNull() || !includesBoostTest(doc, m_cppSnapshot) || !hasBoostTestMacros(doc)) + return false; + return handleBoostTest(futureInterface, doc, m_cppSnapshot, id()); +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttestparser.h b/src/plugins/autotest/boost/boosttestparser.h new file mode 100644 index 0000000000..678b859cc9 --- /dev/null +++ b/src/plugins/autotest/boost/boosttestparser.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "../itestparser.h" +#include "boosttesttreeitem.h" + +namespace Autotest { +namespace Internal { + +class BoostTestParseResult : public TestParseResult +{ +public: + explicit BoostTestParseResult(const Core::Id &id) : TestParseResult(id) {} + TestTreeItem *createTestTreeItem() const override; + // TODO special attributes/states (labeled, timeout,...?) + BoostTestTreeItem::TestStates state = BoostTestTreeItem::Enabled; +}; + +class BoostTestParser : public CppParser +{ +public: + bool processDocument(QFutureInterface<TestParseResultPtr> futureInterface, + const QString &fileName) override; +}; + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttestresult.cpp b/src/plugins/autotest/boost/boosttestresult.cpp new file mode 100644 index 0000000000..cf6d6e7b41 --- /dev/null +++ b/src/plugins/autotest/boost/boosttestresult.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "boosttestresult.h" + +namespace Autotest { +namespace Internal { + +BoostTestResult::BoostTestResult(const QString &id, const QString &projectFile, const QString &name) + : TestResult(id, name), m_projectFile(projectFile) +{ +} + +const QString BoostTestResult::outputString(bool selected) const +{ + const QString &desc = description(); + QString output; + switch (result()) { + case ResultType::Pass: + case ResultType::Fail: + output = m_testCase; + if (selected && !desc.isEmpty()) + output.append('\n').append(desc); + break; + default: + output = desc; + if (!selected) + output = output.split('\n').first(); + } + return output; +} + +bool BoostTestResult::isDirectParentOf(const TestResult *other, bool *needsIntermediate) const +{ + if (!TestResult::isDirectParentOf(other, needsIntermediate)) + return false; + + const BoostTestResult *boostOther = static_cast<const BoostTestResult *>(other); + + if (m_testSuite != boostOther->m_testSuite) + return false; + + if (result() == ResultType::TestStart) { + if (!boostOther->m_testCase.isEmpty()) + return boostOther->m_testSuite == m_testSuite && boostOther->result() != ResultType::TestStart; + + return boostOther->m_testCase == m_testCase; + } + return false; +} + +} // namespace Internal +} // namespace Autotest + diff --git a/src/plugins/autotest/boost/boosttestresult.h b/src/plugins/autotest/boost/boosttestresult.h new file mode 100644 index 0000000000..b27dda16e8 --- /dev/null +++ b/src/plugins/autotest/boost/boosttestresult.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "../testresult.h" + +namespace Autotest { +namespace Internal { + +class BoostTestResult : public TestResult +{ +public: + BoostTestResult(const QString &id, const QString &projectFile, const QString &name); + const QString outputString(bool selected) const override; + + bool isDirectParentOf(const TestResult *other, bool *needsIntermediate) const override; + void setTestSuite(const QString &testSuite) { m_testSuite = testSuite; } + void setTestCase(const QString &testCase) { m_testCase = testCase; } +private: + QString m_projectFile; + QString m_testSuite; + QString m_testCase; +}; + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttestsettings.cpp b/src/plugins/autotest/boost/boosttestsettings.cpp new file mode 100644 index 0000000000..7b1fc855ed --- /dev/null +++ b/src/plugins/autotest/boost/boosttestsettings.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "boosttestsettings.h" + +namespace Autotest { +namespace Internal { + +static const char logLevelKey[] = "LogLevel"; +static const char reportLevelKey[] = "ReportLevel"; +static const char seedKey[] = "Seed"; +static const char randomizeKey[] = "Randomize"; +static const char systemErrorsKey[] = "SystemErrors"; +static const char fpExceptionsKey[] = "FPExceptions"; +static const char memLeaksKey[] = "MemoryLeaks"; + +QString BoostTestSettings::name() const +{ + return QString("BoostTest"); +} + +void BoostTestSettings::fromFrameworkSettings(const QSettings *s) + +{ + logLevel = LogLevel((s->value(logLevelKey, int(LogLevel::All)).toInt())); + reportLevel = ReportLevel((s->value(reportLevelKey, int(ReportLevel::Confirm)).toInt())); + systemErrors = s->value(systemErrorsKey, false).toBool(); + fpExceptions = s->value(fpExceptionsKey, false).toBool(); + memLeaks = s->value(memLeaksKey, true).toBool(); + randomize = s->value(randomizeKey, false).toBool(); + seed = s->value(seedKey, 0).toInt(); +} + +void BoostTestSettings::toFrameworkSettings(QSettings *s) const +{ + s->setValue(logLevelKey, int(logLevel)); + s->setValue(reportLevelKey, int(reportLevel)); + s->setValue(systemErrorsKey, systemErrors); + s->setValue(fpExceptionsKey, fpExceptions); + s->setValue(memLeaksKey, memLeaks); + s->setValue(randomizeKey, randomize); + s->setValue(seedKey, seed); +} + +QString BoostTestSettings::logLevelToOption(const LogLevel logLevel) +{ + switch (logLevel) { + case LogLevel::All: return QString("all"); + case LogLevel::Success: return QString("success"); + case LogLevel::TestSuite: return QString("test_suite"); + case LogLevel::UnitScope: return QString("unit_scope"); + case LogLevel::Message: return QString("message"); + case LogLevel::Error: return QString("error"); + case LogLevel::CppException: return QString("cpp_exception"); + case LogLevel::SystemError: return QString("system_error"); + case LogLevel::FatalError: return QString("fatal_error"); + case LogLevel::Nothing: return QString("nothing"); + case LogLevel::Warning: return QString("warning"); + } + return QString(); +} + +QString BoostTestSettings::reportLevelToOption(const ReportLevel reportLevel) +{ + switch (reportLevel) { + case ReportLevel::Confirm: return QString("confirm"); + case ReportLevel::Short: return QString("short"); + case ReportLevel::Detailed: return QString("detailed"); + case ReportLevel::No: return QString("no"); + } + return QString(); +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttestsettings.h b/src/plugins/autotest/boost/boosttestsettings.h new file mode 100644 index 0000000000..8e10af856f --- /dev/null +++ b/src/plugins/autotest/boost/boosttestsettings.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "../iframeworksettings.h" + +namespace Autotest { +namespace Internal { + +enum class LogLevel +{ + All, + Success, + TestSuite, + UnitScope, + Message, + Warning, + Error, + CppException, + SystemError, + FatalError, + Nothing +}; + +enum class ReportLevel +{ + Confirm, + Short, + Detailed, + No +}; + +class BoostTestSettings : public IFrameworkSettings +{ +public: + BoostTestSettings() = default; + QString name() const override; + static QString logLevelToOption(const LogLevel logLevel); + static QString reportLevelToOption(const ReportLevel reportLevel); + + LogLevel logLevel = LogLevel::Warning; + ReportLevel reportLevel = ReportLevel::Confirm; + int seed = 0; + bool randomize = false; + bool systemErrors = false; + bool fpExceptions = false; + bool memLeaks = true; + +protected: + void fromFrameworkSettings(const QSettings *s) override; + void toFrameworkSettings(QSettings *s) const override; +}; + +} // namespace Internal +} // namespace Autotest + +Q_DECLARE_METATYPE(Autotest::Internal::LogLevel) +Q_DECLARE_METATYPE(Autotest::Internal::ReportLevel) diff --git a/src/plugins/autotest/boost/boosttestsettingspage.cpp b/src/plugins/autotest/boost/boosttestsettingspage.cpp new file mode 100644 index 0000000000..99d13a51da --- /dev/null +++ b/src/plugins/autotest/boost/boosttestsettingspage.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "boosttestsettingspage.h" +#include "boosttestconstants.h" +#include "boosttestsettings.h" +#include "../testframeworkmanager.h" + +#include <coreplugin/icore.h> + +namespace Autotest { +namespace Internal { + +BoostTestSettingsWidget::BoostTestSettingsWidget(QWidget *parent) : + QWidget(parent) +{ + m_ui.setupUi(this); + fillComboBoxes(); + connect(m_ui.randomizeCB, &QCheckBox::toggled, m_ui.seedSB, &QSpinBox::setEnabled); +} + +void BoostTestSettingsWidget::setSettings(const BoostTestSettings &settings) +{ + m_ui.logFormatCB->setCurrentIndex(int(settings.logLevel)); + m_ui.reportLevelCB->setCurrentIndex(int(settings.reportLevel)); + m_ui.randomizeCB->setChecked(settings.randomize); + m_ui.seedSB->setValue(settings.seed); + m_ui.systemErrorCB->setChecked(settings.systemErrors); + m_ui.fpExceptions->setChecked(settings.fpExceptions); + m_ui.memoryLeakCB->setChecked(settings.memLeaks); +} + +BoostTestSettings BoostTestSettingsWidget::settings() const +{ + BoostTestSettings result; + + result.logLevel = LogLevel(m_ui.logFormatCB->currentData().toInt()); + result.reportLevel = ReportLevel(m_ui.reportLevelCB->currentData().toInt()); + result.randomize = m_ui.randomizeCB->isChecked(); + result.seed = m_ui.seedSB->value(); + result.systemErrors = m_ui.systemErrorCB->isChecked(); + result.fpExceptions = m_ui.fpExceptions->isChecked(); + result.memLeaks = m_ui.memoryLeakCB->isChecked(); + return result; +} + +void BoostTestSettingsWidget::fillComboBoxes() +{ + m_ui.logFormatCB->addItem("All", QVariant::fromValue(LogLevel::All)); + m_ui.logFormatCB->addItem("Success", QVariant::fromValue(LogLevel::Success)); + m_ui.logFormatCB->addItem("Test Suite", QVariant::fromValue(LogLevel::TestSuite)); + m_ui.logFormatCB->addItem("Unit Scope", QVariant::fromValue(LogLevel::UnitScope)); + m_ui.logFormatCB->addItem("Message", QVariant::fromValue(LogLevel::Message)); + m_ui.logFormatCB->addItem("Warning", QVariant::fromValue(LogLevel::Warning)); + m_ui.logFormatCB->addItem("Error", QVariant::fromValue(LogLevel::Error)); + m_ui.logFormatCB->addItem("C++ Exception", QVariant::fromValue(LogLevel::CppException)); + m_ui.logFormatCB->addItem("System Error", QVariant::fromValue(LogLevel::SystemError)); + m_ui.logFormatCB->addItem("Fatal Error", QVariant::fromValue(LogLevel::FatalError)); + m_ui.logFormatCB->addItem("Nothing", QVariant::fromValue(LogLevel::Nothing)); + + m_ui.reportLevelCB->addItem("Confirm", QVariant::fromValue(ReportLevel::Confirm)); + m_ui.reportLevelCB->addItem("Short", QVariant::fromValue(ReportLevel::Short)); + m_ui.reportLevelCB->addItem("Detailed", QVariant::fromValue(ReportLevel::Detailed)); + m_ui.reportLevelCB->addItem("No", QVariant::fromValue(ReportLevel::No)); +} + +BoostTestSettingsPage::BoostTestSettingsPage(QSharedPointer<IFrameworkSettings> settings, + const ITestFramework *framework) + : ITestSettingsPage(framework), + m_settings(qSharedPointerCast<BoostTestSettings>(settings)) +{ + setDisplayName(QCoreApplication::translate("BoostTestFramework", + BoostTest::Constants::FRAMEWORK_SETTINGS_CATEGORY)); +} + +QWidget *BoostTestSettingsPage::widget() +{ + if (!m_widget) { + m_widget = new BoostTestSettingsWidget; + m_widget->setSettings(*m_settings); + } + return m_widget; +} + +void BoostTestSettingsPage::apply() +{ + if (!m_widget) // page was not shown at all + return; + + *m_settings = m_widget->settings(); + m_settings->toSettings(Core::ICore::settings()); +} + +} // Internal +} // Autotest diff --git a/src/plugins/autotest/boost/boosttestsettingspage.h b/src/plugins/autotest/boost/boosttestsettingspage.h new file mode 100644 index 0000000000..5e0e65f7b9 --- /dev/null +++ b/src/plugins/autotest/boost/boosttestsettingspage.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ui_boosttestsettingspage.h" +#include "../itestsettingspage.h" + +#include <QPointer> + +namespace Autotest { +namespace Internal { + +class IFrameworkSettings; +class BoostTestSettings; + +class BoostTestSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit BoostTestSettingsWidget(QWidget *parent = nullptr); + + void setSettings(const BoostTestSettings &settings); + BoostTestSettings settings() const; +private: + void fillComboBoxes(); + Ui::BoostSettingsPage m_ui; +}; + +class BoostTestSettingsPage : public ITestSettingsPage +{ + Q_OBJECT +public: + BoostTestSettingsPage(QSharedPointer<IFrameworkSettings> settings, + const ITestFramework *framework); + + QWidget *widget() override; + void apply() override; + +private: + QSharedPointer<BoostTestSettings> m_settings; + QPointer<BoostTestSettingsWidget> m_widget; +}; + +} // Internal +} // Autotest diff --git a/src/plugins/autotest/boost/boosttestsettingspage.ui b/src/plugins/autotest/boost/boosttestsettingspage.ui new file mode 100644 index 0000000000..1596cd7d41 --- /dev/null +++ b/src/plugins/autotest/boost/boosttestsettingspage.ui @@ -0,0 +1,163 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BoostSettingsPage</class> + <widget class="QWidget" name="BoostSettingsPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>309</width> + <height>284</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Log format:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="logFormatCB"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Report level:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="reportLevelCB"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QCheckBox" name="randomizeCB"> + <property name="toolTip"> + <string>Randomize execution order.</string> + </property> + <property name="text"> + <string>Randomize</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Seed:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="seedSB"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>A seed of 0 means no randomization. A value of 1 uses the current time any other value is used as random seed generator.</string> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="systemErrorCB"> + <property name="toolTip"> + <string>Catch or ignore system errors.</string> + </property> + <property name="text"> + <string>Catch system errors</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="fpExceptions"> + <property name="toolTip"> + <string>Enable floating point exception traps.</string> + </property> + <property name="text"> + <string>Floating point exceptions</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="memoryLeakCB"> + <property name="toolTip"> + <string>Enable memory leak detection.</string> + </property> + <property name="text"> + <string>Detect memory leaks</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>84</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/autotest/boost/boosttesttreeitem.cpp b/src/plugins/autotest/boost/boosttesttreeitem.cpp new file mode 100644 index 0000000000..e2e58c28af --- /dev/null +++ b/src/plugins/autotest/boost/boosttesttreeitem.cpp @@ -0,0 +1,365 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "boosttesttreeitem.h" +#include "boosttestconstants.h" +#include "boosttestconfiguration.h" +#include "boosttestparser.h" +#include "../testframeworkmanager.h" + +#include <projectexplorer/session.h> +#include <utils/qtcassert.h> + +#include <QFileInfo> +#include <QRegularExpression> + +namespace Autotest { +namespace Internal { + +TestTreeItem *BoostTestTreeItem::copyWithoutChildren() +{ + BoostTestTreeItem *copied = new BoostTestTreeItem; + copied->copyBasicDataFrom(this); + copied->m_state = m_state; + copied->m_fullName = m_fullName; + return copied; +} + +QVariant BoostTestTreeItem::data(int column, int role) const +{ + switch (role) { + case Qt::DisplayRole: + if (type() == Root) + break; + return QString(name() + nameSuffix()); + case Qt::CheckStateRole: + return checked(); + case ItalicRole: + return false; + case EnabledRole: + return enabled(); + default: + break; + } + return TestTreeItem::data(column, role); +} + +TestTreeItem *BoostTestTreeItem::find(const TestParseResult *result) +{ + QTC_ASSERT(result, return nullptr); + + const BoostTestParseResult *bResult = static_cast<const BoostTestParseResult *>(result); + + switch (type()) { + case Root: + if (TestFrameworkManager::instance()->groupingEnabled(result->frameworkId)) { + const QFileInfo fileInfo(bResult->fileName); + const QFileInfo base(fileInfo.absolutePath()); + for (int row = 0; row < childCount(); ++row) { + BoostTestTreeItem *group = static_cast<BoostTestTreeItem *>(childAt(row)); + if (group->filePath() != base.absoluteFilePath()) + continue; + if (auto groupChild = group->findChildByNameStateAndFile( + bResult->name, bResult->state, bResult->proFile)) { + return groupChild; + } + } + } + return findChildByNameStateAndFile(bResult->name, bResult->state, bResult->proFile); + case GroupNode: + case TestSuite: + return findChildByNameStateAndFile(bResult->name, bResult->state, bResult->proFile); + default: + return nullptr; + } +} + +TestTreeItem *BoostTestTreeItem::findChild(const TestTreeItem *other) +{ + QTC_ASSERT(other, return nullptr); + const Type otherType = other->type(); + + switch (type()) { + case Root: { + TestTreeItem *result = nullptr; + if (otherType == GroupNode) { + result = findChildByNameAndFile(other->name(), other->filePath()); + } else if (otherType == TestSuite) { + auto bOther = static_cast<const BoostTestTreeItem *>(other); + result = findChildByNameStateAndFile(bOther->name(), bOther->state(), + bOther->proFile()); + } + return (result && result->type() == otherType) ? result : nullptr; + } + case GroupNode: { + auto bOther = static_cast<const BoostTestTreeItem *>(other); + return otherType == TestSuite + ? findChildByNameStateAndFile(bOther->name(), bOther->state(), bOther->proFile()) + : nullptr; + } + case TestSuite: { + if (otherType == TestCase) { + return findChildByNameAndFile(other->name(), other->filePath()); + } else if (otherType == TestSuite) { + auto bOther = static_cast<const BoostTestTreeItem *>(other); + return findChildByNameStateAndFile(other->name(), bOther->state(), other->proFile()); + } else { + return nullptr; + } + } + default: + return nullptr; + } +} + +bool BoostTestTreeItem::modify(const TestParseResult *result) +{ + QTC_ASSERT(result, return false); + return (type() == TestCase || type() == TestSuite) + ? modifyTestContent(static_cast<const BoostTestParseResult *>(result)) + : false; +} + +TestTreeItem *BoostTestTreeItem::createParentGroupNode() const +{ + const QFileInfo fileInfo(filePath()); + const QFileInfo base(fileInfo.absolutePath()); + return new BoostTestTreeItem(base.baseName(), fileInfo.absolutePath(), TestTreeItem::GroupNode); +} + +QString BoostTestTreeItem::prependWithParentsSuitePaths(const QString &testName) const +{ + QString prepend = type() == TestSuite ? m_fullName.left(m_fullName.lastIndexOf('/')) + : m_fullName.left(m_fullName.indexOf("::")); + if (prepend.startsWith(BoostTest::Constants::BOOST_MASTER_SUITE)) + prepend = prepend.mid(QString(BoostTest::Constants::BOOST_MASTER_SUITE).length()); + + return prepend + '/' + testName; +} + +static QString handleSpecialFunctionNames(const QString &name) +{ + static const QRegularExpression function(".*\\((.*),.*\\)"); + const QRegularExpressionMatch match = function.match(name); + if (!match.hasMatch()) + return name; + QString result = match.captured(1); + int index = result.lastIndexOf(':'); + if (index != -1) + result = result.mid(index + 1); + result.prepend('*').append('*'); + return result; +} + +QList<TestConfiguration *> BoostTestTreeItem::getAllTestConfigurations() const +{ + QList<TestConfiguration *> result; + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (!project || type() != Root) + return result; + + struct BoostTestCases { + int testCases; + QSet<QString> internalTargets; + }; + + // we only need the unique project files (and number of test cases for the progress indicator) + QHash<QString, BoostTestCases> testsPerProjectfile; + forAllChildren([&testsPerProjectfile](TreeItem *it){ + auto item = static_cast<BoostTestTreeItem *>(it); + if (item->type() != TestSuite) + return; + int funcChildren = 0; + item->forAllChildren([&funcChildren](TreeItem *child){ + if (static_cast<BoostTestTreeItem *>(child)->type() == TestCase) + ++funcChildren; + }); + if (funcChildren) { + testsPerProjectfile[item->proFile()].testCases += funcChildren; + testsPerProjectfile[item->proFile()].internalTargets.unite(item->internalTargets()); + } + }); + + for (auto it = testsPerProjectfile.begin(), end = testsPerProjectfile.end(); it != end; ++it) { + BoostTestConfiguration *config = new BoostTestConfiguration; + config->setProject(project); + config->setProjectFile(it.key()); + config->setTestCaseCount(it.value().testCases); + config->setInternalTargets(it.value().internalTargets); + result.append(config); + } + return result; +} + +QList<TestConfiguration *> BoostTestTreeItem::getSelectedTestConfigurations() const +{ + QList<TestConfiguration *> result; + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (!project || type() != Root) + return result; + + struct BoostTestCases { + QStringList testCases; + QSet<QString> internalTargets; + }; + + QHash<QString, BoostTestCases> testCasesForProjectFile; + forAllChildren([&testCasesForProjectFile](TreeItem *it){ + auto item = static_cast<BoostTestTreeItem *>(it); + if (item->type() != TestCase) + return; + if (!item->enabled()) // ignore child tests known to be disabled when using run selected + return; + if (item->checked() == Qt::Checked) { + QString tcName = item->name(); + if (item->state().testFlag(BoostTestTreeItem::Templated)) + tcName.append("<*"); + tcName = handleSpecialFunctionNames(tcName); + testCasesForProjectFile[item->proFile()].testCases.append( + item->prependWithParentsSuitePaths(tcName)); + testCasesForProjectFile[item->proFile()].internalTargets.unite(item->internalTargets()); + } + }); + + auto end = testCasesForProjectFile.cend(); + for (auto it = testCasesForProjectFile.cbegin(); it != end; ++it) { + BoostTestConfiguration *config = new BoostTestConfiguration; + config->setProject(project); + config->setProjectFile(it.key()); + config->setTestCases(it.value().testCases); + config->setInternalTargets(it.value().internalTargets); + result.append(config); + } + return result; +} + +TestConfiguration *BoostTestTreeItem::testConfiguration() const +{ + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + QTC_ASSERT(project, return nullptr); + + const Type itemType = type(); + if (itemType == TestSuite || itemType == TestCase) { + QStringList testCases; + if (itemType == TestSuite) { + forFirstLevelChildren([&testCases](TestTreeItem *child) { + QTC_ASSERT(child, return); + if (auto boostItem = static_cast<BoostTestTreeItem *>(child)) { + if (boostItem->enabled()) { + QString tcName = handleSpecialFunctionNames(boostItem->name()); + if (boostItem->type() == TestSuite) // execute everything below a suite + tcName.append("/*"); + else if (boostItem->state().testFlag(BoostTestTreeItem::Templated)) + tcName.append("<*"); + testCases.append(boostItem->prependWithParentsSuitePaths(tcName)); + } + } + }); + } else { + QString tcName = name(); + if (state().testFlag(BoostTestTreeItem::Templated)) + tcName.append("<*"); + testCases.append(prependWithParentsSuitePaths(handleSpecialFunctionNames(tcName))); + } + + BoostTestConfiguration *config = new BoostTestConfiguration; + config->setProjectFile(proFile()); + config->setProject(project); + config->setTestCases(testCases); + config->setInternalTargets(internalTargets()); + return config; + } + return nullptr; +} + +TestConfiguration *BoostTestTreeItem::debugConfiguration() const +{ + BoostTestConfiguration *config = static_cast<BoostTestConfiguration *>(testConfiguration()); + if (config) + config->setRunMode(TestRunMode::Debug); + return config; +} + +QString BoostTestTreeItem::nameSuffix() const +{ + static QString markups[] = {QCoreApplication::translate("BoostTestTreeItem", "parameterized"), + QCoreApplication::translate("BoostTestTreeItem", "fixture"), + QCoreApplication::translate("BoostTestTreeItem", "templated")}; + QString suffix; + if (m_state & Parameterized) + suffix = QString(" [") + markups[0]; + if (m_state & Fixture) + suffix += (suffix.isEmpty() ? QString(" [") : QString(", ")) + markups[1]; + if (m_state & Templated) + suffix += (suffix.isEmpty() ? QString(" [") : QString(", ")) + markups[2]; + if (!suffix.isEmpty()) + suffix += ']'; + return suffix; +} + +bool BoostTestTreeItem::enabled() const +{ + if (m_state & ExplicitlyEnabled) + return true; + + if (m_state & Disabled) + return false; + + const TestTreeItem *parent = parentItem(); + if (parent && parent->type() == TestSuite) // take test suites into account + return static_cast<const BoostTestTreeItem *>(parent)->enabled(); + + return true; +} + +TestTreeItem *BoostTestTreeItem::findChildByNameStateAndFile(const QString &name, + BoostTestTreeItem::TestStates state, + const QString &proFile) const +{ + return static_cast<TestTreeItem *>( + findAnyChild([name, state, proFile](const Utils::TreeItem *other){ + const BoostTestTreeItem *boostItem = static_cast<const BoostTestTreeItem *>(other); + return boostItem->proFile() == proFile && boostItem->fullName() == name + && boostItem->state() == state; + })); +} + +bool BoostTestTreeItem::modifyTestContent(const BoostTestParseResult *result) +{ + bool hasBeenModified = modifyLineAndColumn(result); + + if (m_state != result->state) { + m_state = result->state; + hasBeenModified = true; + } + if (m_fullName != result->name) { + m_fullName = result->name; + hasBeenModified = true; + } + return hasBeenModified; +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/boost/boosttesttreeitem.h b/src/plugins/autotest/boost/boosttesttreeitem.h new file mode 100644 index 0000000000..1149720f9d --- /dev/null +++ b/src/plugins/autotest/boost/boosttesttreeitem.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "../testtreeitem.h" + +namespace Autotest { +namespace Internal { + +class BoostTestParseResult; + +class BoostTestTreeItem : public TestTreeItem +{ +public: + enum TestState + { + Enabled = 0x00, + Disabled = 0x01, + ExplicitlyEnabled = 0x02, + + Parameterized = 0x10, + Fixture = 0x20, + Templated = 0x40, + }; + Q_FLAGS(TestState) + Q_DECLARE_FLAGS(TestStates, TestState) + + explicit BoostTestTreeItem(const QString &name = QString(), const QString &filePath = QString(), + Type type = Root) : TestTreeItem(name, filePath, type) {} + +public: + TestTreeItem *copyWithoutChildren() override; + QVariant data(int column, int role) const override; + TestTreeItem *find(const TestParseResult *result) override; + TestTreeItem *findChild(const TestTreeItem *other) override; + bool modify(const TestParseResult *result) override; + TestTreeItem *createParentGroupNode() const override; + + void setFullName(const QString &fullName) { m_fullName = fullName; } + QString fullName() const { return m_fullName; } + void setStates(TestStates states) { m_state = states; } + void setState(TestState state) { m_state |= state; } + TestStates state() const { return m_state; } + + QList<TestConfiguration *> getAllTestConfigurations() const override; + QList<TestConfiguration *> getSelectedTestConfigurations() const override; + bool canProvideTestConfiguration() const override { return type() != Root; } + bool canProvideDebugConfiguration() const override { return canProvideTestConfiguration(); } + TestConfiguration *testConfiguration() const override; + TestConfiguration *debugConfiguration() const override; + +private: + QString nameSuffix() const; + bool enabled() const; + TestTreeItem *findChildByNameStateAndFile(const QString &name, + BoostTestTreeItem::TestStates state, + const QString &proFile) const; + QString prependWithParentsSuitePaths(const QString &testName) const; + bool modifyTestContent(const BoostTestParseResult *result); + TestStates m_state = Enabled; + QString m_fullName; +}; + +struct BoostTestInfo +{ + QString fullName; // formatted like UNIX path + BoostTestTreeItem::TestStates state; + unsigned line; +}; + +typedef QVector<BoostTestInfo> BoostTestInfoList; + +class BoostTestCodeLocationAndType : public TestCodeLocationAndType +{ +public: + BoostTestTreeItem::TestStates m_state; + BoostTestInfoList m_suitesState; +}; + +typedef QVector<BoostTestCodeLocationAndType> BoostTestCodeLocationList; + + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/gtest/gtestoutputreader.cpp b/src/plugins/autotest/gtest/gtestoutputreader.cpp index d66ab7700b..f64155ed42 100644 --- a/src/plugins/autotest/gtest/gtestoutputreader.cpp +++ b/src/plugins/autotest/gtest/gtestoutputreader.cpp @@ -31,7 +31,7 @@ #include <QDir> #include <QFileInfo> -#include <QRegExp> +#include <QRegularExpression> namespace Autotest { namespace Internal { @@ -48,12 +48,11 @@ GTestOutputReader::GTestOutputReader(const QFutureInterface<TestResultPtr> &futu , m_projectFile(projectFile) { if (m_testApplication) { - connect(m_testApplication, - static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), + connect(m_testApplication, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this] (int exitCode, QProcess::ExitStatus /*exitStatus*/) { if (exitCode == 1 && !m_description.isEmpty()) { createAndReportResult(tr("Running tests failed.\n %1\nExecutable: %2") - .arg(m_description).arg(id()), Result::MessageFatal); + .arg(m_description).arg(id()), ResultType::MessageFatal); } // on Windows abort() will result in normal termination, but exit code will be set to 3 if (Utils::HostOsInfo::isWindowsHost() && exitCode == 3) @@ -64,106 +63,108 @@ GTestOutputReader::GTestOutputReader(const QFutureInterface<TestResultPtr> &futu void GTestOutputReader::processOutputLine(const QByteArray &outputLineWithNewLine) { - static QRegExp newTestStarts("^\\[-{10}\\] \\d+ tests? from (.*)$"); - static QRegExp testEnds("^\\[-{10}\\] \\d+ tests? from (.*) \\((.*)\\)$"); - static QRegExp newTestSetStarts("^\\[ RUN \\] (.*)$"); - static QRegExp testSetSuccess("^\\[ OK \\] (.*) \\((.*)\\)$"); - static QRegExp testSetFail("^\\[ FAILED \\] (.*) \\((\\d+ ms)\\)$"); - static QRegExp disabledTests("^ YOU HAVE (\\d+) DISABLED TESTS?$"); - static QRegExp failureLocation("^(.*):(\\d+): Failure$"); - static QRegExp errorLocation("^(.*)\\((\\d+)\\): error:.*$"); - static QRegExp iterations("^Repeating all tests \\(iteration (\\d+)\\) \\. \\. \\.$"); + static const QRegularExpression newTestStarts("^\\[-{10}\\] \\d+ tests? from (.*)$"); + static const QRegularExpression testEnds("^\\[-{10}\\] \\d+ tests? from (.*) \\((.*)\\)$"); + static const QRegularExpression newTestSetStarts("^\\[ RUN \\] (.*)$"); + static const QRegularExpression testSetSuccess("^\\[ OK \\] (.*) \\((.*)\\)$"); + static const QRegularExpression testSetFail("^\\[ FAILED \\] (.*) \\((\\d+ ms)\\)$"); + static const QRegularExpression disabledTests("^ YOU HAVE (\\d+) DISABLED TESTS?$"); + static const QRegularExpression failureLocation("^(.*):(\\d+): Failure$"); + static const QRegularExpression errorLocation("^(.*)\\((\\d+)\\): error:.*$"); + static const QRegularExpression iterations("^Repeating all tests " + "\\(iteration (\\d+)\\) \\. \\. \\.$"); const QString line = QString::fromLatin1(chopLineBreak(outputLineWithNewLine)); if (line.trimmed().isEmpty()) return; + struct ExactMatch : public QRegularExpressionMatch + { + ExactMatch(const QRegularExpressionMatch &other) : QRegularExpressionMatch(other) {} + operator bool() const { return hasMatch(); } + }; + if (!line.startsWith('[')) { m_description.append(line).append('\n'); - if (iterations.exactMatch(line)) { - m_iteration = iterations.cap(1).toInt(); + if (ExactMatch match = iterations.match(line)) { + m_iteration = match.captured(1).toInt(); m_description.clear(); } else if (line.startsWith(QStringLiteral("Note:"))) { m_description = line; if (m_iteration > 1) m_description.append(' ' + tr("(iteration %1)").arg(m_iteration)); TestResultPtr testResult = TestResultPtr(new GTestResult(id(), m_projectFile, QString())); - testResult->setResult(Result::MessageInternal); + testResult->setResult(ResultType::MessageInternal); testResult->setDescription(m_description); reportResult(testResult); m_description.clear(); - } else if (disabledTests.exactMatch(line)) { - TestResultPtr testResult = TestResultPtr(new GTestResult(id(), m_projectFile, QString())); - testResult->setResult(Result::MessageDisabledTests); - int disabled = disabledTests.cap(1).toInt(); - testResult->setDescription(tr("You have %n disabled test(s).", nullptr, disabled)); - testResult->setLine(disabled); // misuse line property to hold number of disabled - reportResult(testResult); + } else if (ExactMatch match = disabledTests.match(line)) { + m_disabled = match.captured(1).toInt(); m_description.clear(); } return; } - if (testEnds.exactMatch(line)) { + if (ExactMatch match = testEnds.match(line)) { TestResultPtr testResult = createDefaultResult(); - testResult->setResult(Result::MessageTestCaseEnd); - testResult->setDescription(tr("Test execution took %1").arg(testEnds.cap(2))); + testResult->setResult(ResultType::TestEnd); + testResult->setDescription(tr("Test execution took %1").arg(match.captured(2))); reportResult(testResult); - m_currentTestName.clear(); - m_currentTestSet.clear(); - } else if (newTestStarts.exactMatch(line)) { - setCurrentTestName(newTestStarts.cap(1)); + m_currentTestSuite.clear(); + m_currentTestCase.clear(); + } else if (ExactMatch match = newTestStarts.match(line)) { + setCurrentTestSuite(match.captured(1)); TestResultPtr testResult = createDefaultResult(); - testResult->setResult(Result::MessageTestCaseStart); + testResult->setResult(ResultType::TestStart); if (m_iteration > 1) { - testResult->setDescription(tr("Repeating test case %1 (iteration %2)") - .arg(m_currentTestName).arg(m_iteration)); + testResult->setDescription(tr("Repeating test suite %1 (iteration %2)") + .arg(m_currentTestSuite).arg(m_iteration)); } else { - testResult->setDescription(tr("Executing test case %1").arg(m_currentTestName)); + testResult->setDescription(tr("Executing test suite %1").arg(m_currentTestSuite)); } reportResult(testResult); - } else if (newTestSetStarts.exactMatch(line)) { - setCurrentTestSet(newTestSetStarts.cap(1)); - TestResultPtr testResult = TestResultPtr(new GTestResult(m_projectFile)); - testResult->setResult(Result::MessageCurrentTest); - testResult->setDescription(tr("Entering test set %1").arg(m_currentTestSet)); + } else if (ExactMatch match = newTestSetStarts.match(line)) { + setCurrentTestCase(match.captured(1)); + TestResultPtr testResult = TestResultPtr(new GTestResult(QString(), m_projectFile, + QString())); + testResult->setResult(ResultType::MessageCurrentTest); + testResult->setDescription(tr("Entering test case %1").arg(m_currentTestCase)); reportResult(testResult); m_description.clear(); - } else if (testSetSuccess.exactMatch(line)) { + } else if (ExactMatch match = testSetSuccess.match(line)) { TestResultPtr testResult = createDefaultResult(); - testResult->setResult(Result::Pass); + testResult->setResult(ResultType::Pass); testResult->setDescription(m_description); reportResult(testResult); m_description.clear(); testResult = createDefaultResult(); - testResult->setResult(Result::MessageInternal); - testResult->setDescription(tr("Execution took %1.").arg(testSetSuccess.cap(2))); + testResult->setResult(ResultType::MessageInternal); + testResult->setDescription(tr("Execution took %1.").arg(match.captured(2))); reportResult(testResult); m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); - } else if (testSetFail.exactMatch(line)) { + } else if (ExactMatch match = testSetFail.match(line)) { TestResultPtr testResult = createDefaultResult(); - testResult->setResult(Result::Fail); + testResult->setResult(ResultType::Fail); m_description.chop(1); QStringList resultDescription; for (const QString &output : m_description.split('\n')) { - QRegExp *match = nullptr; - if (failureLocation.exactMatch(output)) - match = &failureLocation; - else if (errorLocation.exactMatch(output)) - match = &errorLocation; - if (!match) { - resultDescription << output; - continue; + QRegularExpressionMatch innerMatch = failureLocation.match(output); + if (!innerMatch.hasMatch()) { + innerMatch = errorLocation.match(output); + if (!innerMatch.hasMatch()) { + resultDescription << output; + continue; + } } testResult->setDescription(resultDescription.join('\n')); reportResult(testResult); resultDescription.clear(); testResult = createDefaultResult(); - testResult->setResult(Result::MessageLocation); - testResult->setLine(match->cap(2).toInt()); - QString file = constructSourceFilePath(m_buildDir, match->cap(1)); + testResult->setResult(ResultType::MessageLocation); + testResult->setLine(innerMatch.captured(2).toInt()); + QString file = constructSourceFilePath(m_buildDir, innerMatch.captured(1)); if (!file.isEmpty()) testResult->setFileName(file); resultDescription << output; @@ -172,8 +173,8 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLineWithNewLin reportResult(testResult); m_description.clear(); testResult = createDefaultResult(); - testResult->setResult(Result::MessageInternal); - testResult->setDescription(tr("Execution took %1.").arg(testSetFail.cap(2))); + testResult->setResult(ResultType::MessageInternal); + testResult->setDescription(tr("Execution took %1.").arg(match.captured(2))); reportResult(testResult); m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); } @@ -181,8 +182,8 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLineWithNewLin TestResultPtr GTestOutputReader::createDefaultResult() const { - GTestResult *result = new GTestResult(id(), m_projectFile, m_currentTestName); - result->setTestSetName(m_currentTestSet); + GTestResult *result = new GTestResult(id(), m_projectFile, m_currentTestSuite); + result->setTestCaseName(m_currentTestCase); result->setIteration(m_iteration); const TestTreeItem *testItem = result->findTestTreeItem(); @@ -195,14 +196,14 @@ TestResultPtr GTestOutputReader::createDefaultResult() const return TestResultPtr(result); } -void GTestOutputReader::setCurrentTestSet(const QString &testSet) +void GTestOutputReader::setCurrentTestCase(const QString &testCase) { - m_currentTestSet = testSet; + m_currentTestCase = testCase; } -void GTestOutputReader::setCurrentTestName(const QString &testName) +void GTestOutputReader::setCurrentTestSuite(const QString &testSuite) { - m_currentTestName = testName; + m_currentTestSuite = testSuite; } } // namespace Internal diff --git a/src/plugins/autotest/gtest/gtestoutputreader.h b/src/plugins/autotest/gtest/gtestoutputreader.h index 8f29ae73e1..6684b77f44 100644 --- a/src/plugins/autotest/gtest/gtestoutputreader.h +++ b/src/plugins/autotest/gtest/gtestoutputreader.h @@ -48,12 +48,12 @@ protected: TestResultPtr createDefaultResult() const override; private: - void setCurrentTestSet(const QString &testSet); - void setCurrentTestName(const QString &testName); + void setCurrentTestCase(const QString &testCase); + void setCurrentTestSuite(const QString &testSuite); QString m_projectFile; - QString m_currentTestName; - QString m_currentTestSet; + QString m_currentTestSuite; + QString m_currentTestCase; QString m_description; int m_iteration = 1; }; diff --git a/src/plugins/autotest/gtest/gtestparser.cpp b/src/plugins/autotest/gtest/gtestparser.cpp index b21f161f06..f57f774ec0 100644 --- a/src/plugins/autotest/gtest/gtestparser.cpp +++ b/src/plugins/autotest/gtest/gtestparser.cpp @@ -36,7 +36,7 @@ namespace Internal { TestTreeItem *GTestParseResult::createTestTreeItem() const { - if (itemType != TestTreeItem::TestCase && itemType != TestTreeItem::TestFunctionOrSet) + if (itemType != TestTreeItem::TestSuite && itemType != TestTreeItem::TestCase) return nullptr; GTestTreeItem *item = new GTestTreeItem(name, fileName, itemType); item->setProFile(proFile); @@ -110,7 +110,7 @@ static bool handleGTest(QFutureInterface<TestParseResultPtr> futureInterface, for (const GTestCaseSpec &testSpec : result.keys()) { GTestParseResult *parseResult = new GTestParseResult(id); - parseResult->itemType = TestTreeItem::TestCase; + parseResult->itemType = TestTreeItem::TestSuite; parseResult->fileName = filePath; parseResult->name = testSpec.testCaseName; parseResult->parameterized = testSpec.parameterized; @@ -139,12 +139,10 @@ static bool handleGTest(QFutureInterface<TestParseResultPtr> futureInterface, bool GTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface, const QString &fileName) { - if (!m_cppSnapshot.contains(fileName) || !selectedForBuilding(fileName)) + CPlusPlus::Document::Ptr doc = document(fileName); + if (doc.isNull() || !includesGTest(doc, m_cppSnapshot) || !hasGTestNames(doc)) return false; - CPlusPlus::Document::Ptr document = m_cppSnapshot.find(fileName).value(); - if (!includesGTest(document, m_cppSnapshot) || !hasGTestNames(document)) - return false; - return handleGTest(futureInterface, document, m_cppSnapshot, id()); + return handleGTest(futureInterface, doc, m_cppSnapshot, id()); } } // namespace Internal diff --git a/src/plugins/autotest/gtest/gtestresult.cpp b/src/plugins/autotest/gtest/gtestresult.cpp index 645f1af0a8..b2f3aae67b 100644 --- a/src/plugins/autotest/gtest/gtestresult.cpp +++ b/src/plugins/autotest/gtest/gtestresult.cpp @@ -30,14 +30,11 @@ #include <coreplugin/id.h> +#include <QRegularExpression> + namespace Autotest { namespace Internal { -GTestResult::GTestResult(const QString &projectFile, const QString &name) - : TestResult(name), m_projectFile(projectFile) -{ -} - GTestResult::GTestResult(const QString &id, const QString &projectFile, const QString &name) : TestResult(id, name), m_projectFile(projectFile) @@ -49,9 +46,9 @@ const QString GTestResult::outputString(bool selected) const const QString &desc = description(); QString output; switch (result()) { - case Result::Pass: - case Result::Fail: - output = m_testSetName; + case ResultType::Pass: + case ResultType::Fail: + output = m_testCaseName; if (selected && !desc.isEmpty()) output.append('\n').append(desc); break; @@ -68,22 +65,20 @@ bool GTestResult::isDirectParentOf(const TestResult *other, bool *needsIntermedi if (!TestResult::isDirectParentOf(other, needsIntermediate)) return false; - if (result() == Result::MessageDisabledTests) - return false; const GTestResult *gtOther = static_cast<const GTestResult *>(other); - if (m_testSetName == gtOther->m_testSetName) { - const Result::Type otherResult = other->result(); - if (otherResult == Result::MessageInternal || otherResult == Result::MessageLocation) - return result() != Result::MessageInternal && result() != Result::MessageLocation; + if (m_testCaseName == gtOther->m_testCaseName) { + const ResultType otherResult = other->result(); + if (otherResult == ResultType::MessageInternal || otherResult == ResultType::MessageLocation) + return result() != ResultType::MessageInternal && result() != ResultType::MessageLocation; } if (m_iteration != gtOther->m_iteration) return false; - return isTest() && gtOther->isTestSet(); + return isTestSuite() && gtOther->isTestCase(); } static QString normalizeName(const QString &name) { - static QRegExp parameterIndex("/\\d+"); + static QRegularExpression parameterIndex("/\\d+"); QString nameWithoutParameterIndices = name; nameWithoutParameterIndices.remove(parameterIndex); @@ -117,24 +112,24 @@ bool GTestResult::matches(const TestTreeItem *treeItem) const if (treeItem->proFile() != m_projectFile) return false; - if (isTest()) - return matchesTestCase(treeItem); + if (isTestSuite()) + return matchesTestSuite(treeItem); - return matchesTestFunctionOrSet(treeItem); + return matchesTestCase(treeItem); } -bool GTestResult::matchesTestFunctionOrSet(const TestTreeItem *treeItem) const +bool GTestResult::matchesTestCase(const TestTreeItem *treeItem) const { - if (treeItem->type() != TestTreeItem::TestFunctionOrSet) + if (treeItem->type() != TestTreeItem::TestCase) return false; - const QString testItemTestSet = treeItem->parentItem()->name() + '.' + treeItem->name(); - return testItemTestSet == normalizeName(m_testSetName); + const QString testItemTestCase = treeItem->parentItem()->name() + '.' + treeItem->name(); + return testItemTestCase == normalizeName(m_testCaseName); } -bool GTestResult::matchesTestCase(const TestTreeItem *treeItem) const +bool GTestResult::matchesTestSuite(const TestTreeItem *treeItem) const { - if (treeItem->type() != TestTreeItem::TestCase) + if (treeItem->type() != TestTreeItem::TestSuite) return false; return treeItem->name() == normalizeTestName(name()); diff --git a/src/plugins/autotest/gtest/gtestresult.h b/src/plugins/autotest/gtest/gtestresult.h index 9e2d2557d8..a792213327 100644 --- a/src/plugins/autotest/gtest/gtestresult.h +++ b/src/plugins/autotest/gtest/gtestresult.h @@ -33,24 +33,23 @@ namespace Internal { class GTestResult : public TestResult { public: - explicit GTestResult(const QString &projectFile, const QString &name = QString()); GTestResult(const QString &id, const QString &projectFile, const QString &name); const QString outputString(bool selected) const override; - void setTestSetName(const QString &testSetName) { m_testSetName = testSetName; } + void setTestCaseName(const QString &testSetName) { m_testCaseName = testSetName; } void setIteration(int iteration) { m_iteration = iteration; } bool isDirectParentOf(const TestResult *other, bool *needsIntermediate) const override; virtual const TestTreeItem *findTestTreeItem() const override; private: - bool isTest() const { return m_testSetName.isEmpty(); } - bool isTestSet() const { return !m_testSetName.isEmpty(); } + bool isTestSuite() const { return m_testCaseName.isEmpty(); } + bool isTestCase() const { return !m_testCaseName.isEmpty(); } bool matches(const TestTreeItem *item) const; - bool matchesTestFunctionOrSet(const TestTreeItem *treeItem) const; bool matchesTestCase(const TestTreeItem *treeItem) const; + bool matchesTestSuite(const TestTreeItem *treeItem) const; - QString m_testSetName; + QString m_testCaseName; QString m_projectFile; int m_iteration = 1; }; diff --git a/src/plugins/autotest/gtest/gtesttreeitem.cpp b/src/plugins/autotest/gtest/gtesttreeitem.cpp index 335979131a..c344669b3c 100644 --- a/src/plugins/autotest/gtest/gtesttreeitem.cpp +++ b/src/plugins/autotest/gtest/gtesttreeitem.cpp @@ -37,7 +37,7 @@ #include <utils/qtcassert.h> #include <utils/theme/theme.h> -#include <QRegExp> +#include <QRegularExpression> namespace Autotest { namespace Internal { @@ -71,6 +71,20 @@ TestTreeItem *GTestTreeItem::copyWithoutChildren() return copied; } +static QString wildCardPattern(const QString &original) +{ + QString pattern = original; + pattern.replace('.', "\\."); + pattern.replace('$', "\\$"); + pattern.replace('(', "\\(").replace(')', "\\)"); + pattern.replace('[', "\\[").replace(']', "\\]"); + pattern.replace('{', "\\{").replace('}', "\\}"); + pattern.replace('+', "\\+"); + pattern.replace('*', ".*"); + pattern.replace('?', '.'); + return pattern; +} + static bool matchesFilter(const QString &filter, const QString &fullTestName) { QStringList positive; @@ -88,13 +102,13 @@ static bool matchesFilter(const QString &filter, const QString &fullTestName) testName.append('.'); for (const QString &curr : negative) { - QRegExp regex(curr, Qt::CaseSensitive, QRegExp::Wildcard); - if (regex.exactMatch(testName)) + QRegularExpression regex(wildCardPattern(curr)); + if (regex.match(testName).hasMatch()) return false; } for (const QString &curr : positive) { - QRegExp regex(curr, Qt::CaseSensitive, QRegExp::Wildcard); - if (regex.exactMatch(testName)) + QRegularExpression regex(wildCardPattern(curr)); + if (regex.match(testName).hasMatch()) return true; } return positive.isEmpty(); @@ -130,8 +144,8 @@ QVariant GTestTreeItem::data(int column, int role) const switch (type()) { case Root: case GroupNode: + case TestSuite: case TestCase: - case TestFunctionOrSet: return checked(); default: return QVariant(); @@ -153,7 +167,7 @@ TestConfiguration *GTestTreeItem::testConfiguration() const GTestConfiguration *config = nullptr; switch (type()) { - case TestCase: { + case TestSuite: { const QString &testSpecifier = gtestFilter(state()).arg(name()).arg('*'); if (int count = childCount()) { config = new GTestConfiguration; @@ -164,7 +178,7 @@ TestConfiguration *GTestTreeItem::testConfiguration() const } break; } - case TestFunctionOrSet: { + case TestCase: { GTestTreeItem *parent = static_cast<GTestTreeItem *>(parentItem()); if (!parent) return nullptr; @@ -191,7 +205,7 @@ TestConfiguration *GTestTreeItem::debugConfiguration() const return config; } -struct TestCases +struct GTestCases { QStringList filters; int testSetCount = 0; @@ -199,7 +213,7 @@ struct TestCases }; static void collectTestInfo(const GTestTreeItem *item, - QHash<QString, TestCases> &testCasesForProFile, + QHash<QString, GTestCases> &testCasesForProFile, bool ignoreCheckState) { QTC_ASSERT(item, return); @@ -212,7 +226,7 @@ static void collectTestInfo(const GTestTreeItem *item, } const int childCount = item->childCount(); QTC_ASSERT(childCount != 0, return); - QTC_ASSERT(item->type() == TestTreeItem::TestCase, return); + QTC_ASSERT(item->type() == TestTreeItem::TestSuite, return); if (ignoreCheckState || item->checked() == Qt::Checked) { const QString &projectFile = item->childAt(0)->proFile(); testCasesForProFile[projectFile].filters.append( @@ -221,7 +235,7 @@ static void collectTestInfo(const GTestTreeItem *item, testCasesForProFile[projectFile].internalTargets.unite(item->internalTargets()); } else if (item->checked() == Qt::PartiallyChecked) { item->forFirstLevelChildren([&testCasesForProFile, item](TestTreeItem *child){ - QTC_ASSERT(child->type() == TestTreeItem::TestFunctionOrSet, return); + QTC_ASSERT(child->type() == TestTreeItem::TestCase, return); if (child->checked() == Qt::Checked) { testCasesForProFile[child->proFile()].filters.append( gtestFilter(item->state()).arg(item->name()).arg(child->name())); @@ -239,7 +253,7 @@ QList<TestConfiguration *> GTestTreeItem::getTestConfigurations(bool ignoreCheck if (!project || type() != Root) return result; - QHash<QString, TestCases> testCasesForProFile; + QHash<QString, GTestCases> testCasesForProFile; for (int row = 0, count = childCount(); row < count; ++row) { auto child = static_cast<const GTestTreeItem *>(childAt(row)); collectTestInfo(child, testCasesForProFile, ignoreCheckState); @@ -271,21 +285,21 @@ QList<TestConfiguration *> GTestTreeItem::getSelectedTestConfigurations() const return getTestConfigurations(false); } -QList<TestConfiguration *> GTestTreeItem::getTestConfigurationsForFile(const Utils::FileName &fileName) const +QList<TestConfiguration *> GTestTreeItem::getTestConfigurationsForFile(const Utils::FilePath &fileName) const { QList<TestConfiguration *> result; ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); if (!project || type() != Root) return result; - QHash<QString, TestCases> testCases; + QHash<QString, GTestCases> testCases; const QString &file = fileName.toString(); forAllChildren([&testCases, &file](TestTreeItem *node) { - if (node->type() == Type::TestFunctionOrSet && node->filePath() == file) { + if (node->type() == Type::TestCase && node->filePath() == file) { QTC_ASSERT(node->parentItem(), return); const GTestTreeItem *testCase = static_cast<GTestTreeItem *>(node->parentItem()); - QTC_ASSERT(testCase->type() == Type::TestCase, return); - TestCases &cases = testCases[testCase->proFile()]; + QTC_ASSERT(testCase->type() == Type::TestSuite, return); + GTestCases &cases = testCases[testCase->proFile()]; cases.filters.append( gtestFilter(testCase->state()).arg(testCase->name(), node->name())); cases.internalTargets.unite(node->internalTargets()); @@ -351,7 +365,7 @@ TestTreeItem *GTestTreeItem::find(const TestParseResult *result) return findChildByNameStateAndFile(parseResult->name, states, parseResult->proFile); case GroupNode: return findChildByNameStateAndFile(parseResult->name, states, parseResult->proFile); - case TestCase: + case TestSuite: return findChildByNameAndFile(result->name, result->fileName); default: return nullptr; @@ -367,7 +381,7 @@ TestTreeItem *GTestTreeItem::findChild(const TestTreeItem *other) TestTreeItem *result = nullptr; if (otherType == GroupNode) { result = findChildByNameAndFile(other->name(), other->filePath()); - } else if (otherType == TestCase) { + } else if (otherType == TestSuite) { auto gtOther = static_cast<const GTestTreeItem *>(other); result = findChildByNameStateAndFile(gtOther->name(), gtOther->state(), gtOther->proFile()); @@ -376,12 +390,12 @@ TestTreeItem *GTestTreeItem::findChild(const TestTreeItem *other) } case GroupNode: { auto gtOther = static_cast<const GTestTreeItem *>(other); - return otherType == TestCase + return otherType == TestSuite ? findChildByNameStateAndFile(gtOther->name(), gtOther->state(), gtOther->proFile()) : nullptr; } - case TestCase: - return otherType == TestFunctionOrSet + case TestSuite: + return otherType == TestCase ? findChildByNameAndFile(other->name(), other->filePath()) : nullptr; default: @@ -394,7 +408,7 @@ bool GTestTreeItem::modify(const TestParseResult *result) QTC_ASSERT(result, return false); switch (type()) { - case TestFunctionOrSet: + case TestCase: return modifyTestSetContent(static_cast<const GTestParseResult *>(result)); default: return false; @@ -489,11 +503,11 @@ bool GTestTreeItem::isGroupNodeFor(const TestTreeItem *other) const return QFileInfo(other->filePath()).absolutePath() == filePath(); } else { // GTestFilter QString fullName; - if (other->type() == TestCase) { + if (other->type() == TestSuite) { fullName = other->name(); if (other->childCount()) fullName += '.' + other->childAt(0)->name(); - } else if (other->type() == TestFunctionOrSet) { + } else if (other->type() == TestCase) { QTC_ASSERT(other->parentItem(), return false); fullName = other->parentItem()->name() + '.' + other->name(); } else if (other->type() == GroupNode) { // can happen on a rebuild if only filter changes @@ -511,12 +525,12 @@ bool GTestTreeItem::isGroupNodeFor(const TestTreeItem *other) const bool GTestTreeItem::isGroupable() const { - return type() == TestCase; + return type() == TestSuite; } TestTreeItem *GTestTreeItem::applyFilters() { - if (type() != TestCase) + if (type() != TestSuite) return nullptr; if (GTestFramework::groupMode() != GTest::Constants::GTestFilter) @@ -540,5 +554,10 @@ TestTreeItem *GTestTreeItem::applyFilters() return filtered; } +bool GTestTreeItem::shouldBeAddedAfterFiltering() const +{ + return type() == TestTreeItem::TestCase || childCount(); +} + } // namespace Internal } // namespace Autotest diff --git a/src/plugins/autotest/gtest/gtesttreeitem.h b/src/plugins/autotest/gtest/gtesttreeitem.h index 8f1300007f..3b93116ffc 100644 --- a/src/plugins/autotest/gtest/gtesttreeitem.h +++ b/src/plugins/autotest/gtest/gtesttreeitem.h @@ -57,7 +57,7 @@ public: TestConfiguration *debugConfiguration() const override; QList<TestConfiguration *> getAllTestConfigurations() const override; QList<TestConfiguration *> getSelectedTestConfigurations() const override; - QList<TestConfiguration *> getTestConfigurationsForFile(const Utils::FileName &fileName) const override; + QList<TestConfiguration *> getTestConfigurationsForFile(const Utils::FilePath &fileName) const override; TestTreeItem *find(const TestParseResult *result) override; TestTreeItem *findChild(const TestTreeItem *other) override; bool modify(const TestParseResult *result) override; @@ -74,6 +74,7 @@ public: bool isGroupNodeFor(const TestTreeItem *other) const override; bool isGroupable() const override; TestTreeItem *applyFilters() override; + bool shouldBeAddedAfterFiltering() const override; private: bool modifyTestSetContent(const GTestParseResult *result); QList<TestConfiguration *> getTestConfigurations(bool ignoreCheckState) const; diff --git a/src/plugins/autotest/gtest/gtestvisitors.cpp b/src/plugins/autotest/gtest/gtestvisitors.cpp index 2cb8a70c31..f7104ff93d 100644 --- a/src/plugins/autotest/gtest/gtestvisitors.cpp +++ b/src/plugins/autotest/gtest/gtestvisitors.cpp @@ -78,7 +78,7 @@ bool GTestVisitor::visit(CPlusPlus::FunctionDefinitionAST *ast) locationAndType.m_name = testName; locationAndType.m_line = line; locationAndType.m_column = column - 1; - locationAndType.m_type = TestTreeItem::TestFunctionOrSet; + locationAndType.m_type = TestTreeItem::TestCase; locationAndType.m_state = disabled ? GTestTreeItem::Disabled : GTestTreeItem::Enabled; GTestCaseSpec spec; diff --git a/src/plugins/autotest/images/data@2x.png b/src/plugins/autotest/images/data@2x.png Binary files differnew file mode 100644 index 0000000000..26f71a9ad0 --- /dev/null +++ b/src/plugins/autotest/images/data@2x.png diff --git a/src/plugins/autotest/images/sort.png b/src/plugins/autotest/images/sort.png Binary files differdeleted file mode 100644 index c15eb56d50..0000000000 --- a/src/plugins/autotest/images/sort.png +++ /dev/null diff --git a/src/plugins/autotest/images/sort@2x.png b/src/plugins/autotest/images/sort@2x.png Binary files differdeleted file mode 100644 index 1a2e5d9520..0000000000 --- a/src/plugins/autotest/images/sort@2x.png +++ /dev/null diff --git a/src/plugins/autotest/images/suite.png b/src/plugins/autotest/images/suite.png Binary files differnew file mode 100644 index 0000000000..91fc50bf32 --- /dev/null +++ b/src/plugins/autotest/images/suite.png diff --git a/src/plugins/autotest/images/suite@2x.png b/src/plugins/autotest/images/suite@2x.png Binary files differnew file mode 100644 index 0000000000..958480af60 --- /dev/null +++ b/src/plugins/autotest/images/suite@2x.png diff --git a/src/plugins/autotest/itestparser.cpp b/src/plugins/autotest/itestparser.cpp index 9d06320322..b7e1f15245 100644 --- a/src/plugins/autotest/itestparser.cpp +++ b/src/plugins/autotest/itestparser.cpp @@ -78,5 +78,10 @@ void CppParser::release() m_workingCopy = CppTools::WorkingCopy(); } +CPlusPlus::Document::Ptr CppParser::document(const QString &fileName) +{ + return selectedForBuilding(fileName) ? m_cppSnapshot.document(fileName) : nullptr; +} + } // namespace Internal } // namespace Autotest diff --git a/src/plugins/autotest/itestparser.h b/src/plugins/autotest/itestparser.h index b79b3c131b..b69b23b253 100644 --- a/src/plugins/autotest/itestparser.h +++ b/src/plugins/autotest/itestparser.h @@ -81,6 +81,8 @@ public: static QByteArray getFileContent(const QString &filePath); void release() override; + CPlusPlus::Document::Ptr document(const QString &fileName); + protected: CPlusPlus::Snapshot m_cppSnapshot; CppTools::WorkingCopy m_workingCopy; diff --git a/src/plugins/autotest/qtest/qttestoutputreader.cpp b/src/plugins/autotest/qtest/qttestoutputreader.cpp index a74c24dccd..d71fd65509 100644 --- a/src/plugins/autotest/qtest/qttestoutputreader.cpp +++ b/src/plugins/autotest/qtest/qttestoutputreader.cpp @@ -34,6 +34,8 @@ #include <QFileInfo> #include <QRegularExpression> +#include <cctype> + namespace Autotest { namespace Internal { @@ -188,6 +190,15 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine) if (m_className.isEmpty() && outputLine.trimmed().isEmpty()) return; + if (m_expectTag) { + for (auto ch : outputLine) { + if (std::isspace(ch)) + continue; + if (ch != '<') + return; + break; + } + } m_xmlReader.addData(QString::fromUtf8(outputLine)); while (!m_xmlReader.atEnd()) { if (m_futureInterface.isCanceled()) @@ -221,7 +232,7 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine) m_description.clear(); m_duration.clear(); m_file.clear(); - m_result = Result::Invalid; + m_result = ResultType::Invalid; m_lineNumber = 0; const QXmlStreamAttributes &attributes = m_xmlReader.attributes(); m_result = TestResult::resultFromString( @@ -237,24 +248,25 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine) const int iterations = attributes.value(QStringLiteral("iterations")).toInt(); m_dataTag = attributes.value(QStringLiteral("tag")).toString(); m_description = constructBenchmarkInformation(metric, value, iterations); - m_result = Result::Benchmark; + m_result = ResultType::Benchmark; } else if (currentTag == QStringLiteral("DataTag")) { m_cdataMode = DataTag; } else if (currentTag == QStringLiteral("Description")) { m_cdataMode = Description; } else if (currentTag == QStringLiteral("QtVersion")) { - m_result = Result::MessageInternal; + m_result = ResultType::MessageInternal; m_cdataMode = QtVersion; } else if (currentTag == QStringLiteral("QtBuild")) { - m_result = Result::MessageInternal; + m_result = ResultType::MessageInternal; m_cdataMode = QtBuild; } else if (currentTag == QStringLiteral("QTestVersion")) { - m_result = Result::MessageInternal; + m_result = ResultType::MessageInternal; m_cdataMode = QTestVersion; } break; } case QXmlStreamReader::Characters: { + m_expectTag = false; QStringRef text = m_xmlReader.text().trimmed(); if (text.isEmpty()) break; @@ -285,6 +297,7 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine) break; } case QXmlStreamReader::EndElement: { + m_expectTag = true; m_cdataMode = None; const QStringRef currentTag = m_xmlReader.name(); if (currentTag == QStringLiteral("TestFunction")) { @@ -308,7 +321,7 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine) && m_xmlReader.error() != QXmlStreamReader::PrematureEndOfDocumentError) { createAndReportResult(tr("XML parsing failed.") + QString(" (%1) ").arg(m_xmlReader.error()) - + m_xmlReader.errorString(), Result::MessageFatal); + + m_xmlReader.errorString(), ResultType::MessageFatal); } break; } @@ -317,7 +330,7 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine) static QStringList extractFunctionInformation(const QString &testClassName, const QString &lineWithoutResultType, - Result::Type resultType) + ResultType resultType) { static QRegularExpression classInformation("^(.+?)\\((.*?)\\)(.*)$"); QStringList result; @@ -327,7 +340,7 @@ static QStringList extractFunctionInformation(const QString &testClassName, QTC_ASSERT(fullQualifiedFunc.startsWith(testClassName + "::"), return result); fullQualifiedFunc = fullQualifiedFunc.mid(testClassName.length() + 2); result.append(fullQualifiedFunc); - if (resultType == Result::Benchmark) { // tag is displayed differently + if (resultType == ResultType::Benchmark) { // tag is displayed differently QString possiblyTag = match.captured(3); if (!possiblyTag.isEmpty()) possiblyTag = possiblyTag.mid(2, possiblyTag.length() - 4); @@ -452,7 +465,7 @@ void QtTestOutputReader::processSummaryFinishOutput() sendFinishMessage(false); m_className.clear(); m_description.clear(); - m_result = Result::Invalid; + m_result = ResultType::Invalid; m_file.clear(); m_lineNumber = 0; } @@ -478,16 +491,16 @@ void QtTestOutputReader::sendCompleteInformation() void QtTestOutputReader::sendMessageCurrentTest() { - TestResultPtr testResult = TestResultPtr(new QtTestResult(m_projectFile, m_testType)); - testResult->setResult(Result::MessageCurrentTest); + QtTestResult *testResult = new QtTestResult(QString(), m_projectFile, m_testType, QString()); + testResult->setResult(ResultType::MessageCurrentTest); testResult->setDescription(tr("Entering test function %1::%2").arg(m_className, m_testCase)); - reportResult(testResult); + reportResult(TestResultPtr(testResult)); } void QtTestOutputReader::sendStartMessage(bool isFunction) { TestResultPtr testResult = createDefaultResult(); - testResult->setResult(Result::MessageTestCaseStart); + testResult->setResult(ResultType::TestStart); testResult->setDescription(isFunction ? tr("Executing test function %1").arg(m_testCase) : tr("Executing test case %1").arg(m_className)); const TestTreeItem *testItem = testResult->findTestTreeItem(); @@ -501,7 +514,7 @@ void QtTestOutputReader::sendStartMessage(bool isFunction) void QtTestOutputReader::sendFinishMessage(bool isFunction) { TestResultPtr testResult = createDefaultResult(); - testResult->setResult(Result::MessageTestCaseEnd); + testResult->setResult(ResultType::TestEnd); if (!m_duration.isEmpty()) { testResult->setDescription(isFunction ? tr("Execution took %1 ms.").arg(m_duration) : tr("Test execution took %1 ms.").arg(m_duration)); @@ -515,15 +528,15 @@ void QtTestOutputReader::sendFinishMessage(bool isFunction) void QtTestOutputReader::handleAndSendConfigMessage(const QRegularExpressionMatch &config) { TestResultPtr testResult = createDefaultResult(); - testResult->setResult(Result::MessageInternal); + testResult->setResult(ResultType::MessageInternal); testResult->setDescription(trQtVersion(config.captured(3))); reportResult(testResult); testResult = createDefaultResult(); - testResult->setResult(Result::MessageInternal); + testResult->setResult(ResultType::MessageInternal); testResult->setDescription(trQtBuild(config.captured(2))); reportResult(testResult); testResult = createDefaultResult(); - testResult->setResult(Result::MessageInternal); + testResult->setResult(ResultType::MessageInternal); testResult->setDescription(trQtestVersion(config.captured(1))); reportResult(testResult); } diff --git a/src/plugins/autotest/qtest/qttestoutputreader.h b/src/plugins/autotest/qtest/qttestoutputreader.h index a95a51a662..ce2b35416c 100644 --- a/src/plugins/autotest/qtest/qttestoutputreader.h +++ b/src/plugins/autotest/qtest/qttestoutputreader.h @@ -83,7 +83,7 @@ private: QString m_testCase; QString m_formerTestCase; QString m_dataTag; - Result::Type m_result = Result::Invalid; + ResultType m_result = ResultType::Invalid; QString m_description; QString m_file; int m_lineNumber = 0; @@ -91,7 +91,7 @@ private: QXmlStreamReader m_xmlReader; OutputMode m_mode = XML; TestType m_testType = TestType::QtTest; - + bool m_expectTag = true; }; } // namespace Internal diff --git a/src/plugins/autotest/qtest/qttestparser.cpp b/src/plugins/autotest/qtest/qttestparser.cpp index 234aeca79f..ddedf4dc52 100644 --- a/src/plugins/autotest/qtest/qttestparser.cpp +++ b/src/plugins/autotest/qtest/qttestparser.cpp @@ -389,9 +389,9 @@ void QtTestParser::release() bool QtTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface, const QString &fileName) { - if (!m_cppSnapshot.contains(fileName) || !selectedForBuilding(fileName)) + CPlusPlus::Document::Ptr doc = document(fileName); + if (doc.isNull()) return false; - CPlusPlus::Document::Ptr doc = m_cppSnapshot.find(fileName).value(); const QString &oldName = m_testCaseNames.value(fileName); const QStringList &alternativeFiles = m_alternativeFiles.values(fileName); if ((!includesQtTest(doc, m_cppSnapshot) || !qtTestLibDefined(fileName)) && oldName.isEmpty()) diff --git a/src/plugins/autotest/qtest/qttestresult.cpp b/src/plugins/autotest/qtest/qttestresult.cpp index 5cb7fd0402..3e85c36e68 100644 --- a/src/plugins/autotest/qtest/qttestresult.cpp +++ b/src/plugins/autotest/qtest/qttestresult.cpp @@ -34,11 +34,6 @@ namespace Autotest { namespace Internal { -QtTestResult::QtTestResult(const QString &projectFile, TestType type, const QString &className) - : TestResult(className), m_projectFile(projectFile), m_type(type) -{ -} - QtTestResult::QtTestResult(const QString &id, const QString &projectFile, TestType type, const QString &className) : TestResult(id, className), m_projectFile(projectFile), m_type(type) @@ -51,12 +46,12 @@ const QString QtTestResult::outputString(bool selected) const const QString &className = name(); QString output; switch (result()) { - case Result::Pass: - case Result::Fail: - case Result::ExpectedFail: - case Result::UnexpectedPass: - case Result::BlacklistedFail: - case Result::BlacklistedPass: + case ResultType::Pass: + case ResultType::Fail: + case ResultType::ExpectedFail: + case ResultType::UnexpectedPass: + case ResultType::BlacklistedFail: + case ResultType::BlacklistedPass: output = className + "::" + m_function; if (!m_dataTag.isEmpty()) output.append(QString(" (%1)").arg(m_dataTag)); @@ -64,7 +59,7 @@ const QString QtTestResult::outputString(bool selected) const output.append('\n').append(desc); } break; - case Result::Benchmark: + case ResultType::Benchmark: output = className + "::" + m_function; if (!m_dataTag.isEmpty()) output.append(QString(" (%1)").arg(m_dataTag)); @@ -89,12 +84,12 @@ bool QtTestResult::isDirectParentOf(const TestResult *other, bool *needsIntermed return false; const QtTestResult *qtOther = static_cast<const QtTestResult *>(other); - if (TestResult::isMessageCaseStart(result())) { + if (result() == ResultType::TestStart) { if (qtOther->isDataTag()) { if (qtOther->m_function == m_function) { if (m_dataTag.isEmpty()) { // avoid adding function's TestCaseEnd to the data tag - *needsIntermediate = qtOther->result() != Result::MessageTestCaseEnd; + *needsIntermediate = qtOther->result() != ResultType::TestEnd; return true; } return qtOther->m_dataTag == m_dataTag; @@ -164,7 +159,7 @@ bool QtTestResult::matches(const TestTreeItem *item) const if (item->proFile() != m_projectFile) return false; return matchesTestCase(item); - case TestTreeItem::TestFunctionOrSet: + case TestTreeItem::TestFunction: case TestTreeItem::TestSpecialFunction: if (!isTestFunction()) return false; diff --git a/src/plugins/autotest/qtest/qttestresult.h b/src/plugins/autotest/qtest/qttestresult.h index a0735a90c7..f3f653cd1a 100644 --- a/src/plugins/autotest/qtest/qttestresult.h +++ b/src/plugins/autotest/qtest/qttestresult.h @@ -34,7 +34,6 @@ namespace Internal { class QtTestResult : public TestResult { public: - QtTestResult(const QString &projectFile, TestType type, const QString &className = QString()); QtTestResult(const QString &id, const QString &projectFile, TestType type, const QString &className); const QString outputString(bool selected) const override; diff --git a/src/plugins/autotest/qtest/qttesttreeitem.cpp b/src/plugins/autotest/qtest/qttesttreeitem.cpp index 3fe96b6f39..7069e29961 100644 --- a/src/plugins/autotest/qtest/qttesttreeitem.cpp +++ b/src/plugins/autotest/qtest/qttesttreeitem.cpp @@ -82,7 +82,7 @@ Qt::ItemFlags QtTestTreeItem::flags(int column) const switch (type()) { case TestDataTag: return defaultFlags | Qt::ItemIsUserCheckable; - case TestFunctionOrSet: + case TestFunction: return defaultFlags | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable; default: return TestTreeItem::flags(column); @@ -93,7 +93,7 @@ bool QtTestTreeItem::canProvideTestConfiguration() const { switch (type()) { case TestCase: - case TestFunctionOrSet: + case TestFunction: case TestDataTag: return true; default: @@ -119,7 +119,7 @@ TestConfiguration *QtTestTreeItem::testConfiguration() const config->setProjectFile(proFile()); config->setProject(project); break; - case TestFunctionOrSet: { + case TestFunction: { TestTreeItem *parent = parentItem(); config = new QtTestConfiguration(); config->setTestCases(QStringList(name())); @@ -234,7 +234,7 @@ QList<TestConfiguration *> QtTestTreeItem::getSelectedTestConfigurations() const return result; } -QList<TestConfiguration *> QtTestTreeItem::getTestConfigurationsForFile(const Utils::FileName &fileName) const +QList<TestConfiguration *> QtTestTreeItem::getTestConfigurationsForFile(const Utils::FilePath &fileName) const { QList<TestConfiguration *> result; @@ -245,7 +245,7 @@ QList<TestConfiguration *> QtTestTreeItem::getTestConfigurationsForFile(const Ut QHash<TestTreeItem *, QStringList> testFunctions; const QString &file = fileName.toString(); forAllChildren([&testFunctions, &file](TestTreeItem *node) { - if (node->type() == Type::TestFunctionOrSet && node->filePath() == file) { + if (node->type() == Type::TestFunction && node->filePath() == file) { QTC_ASSERT(node->parentItem(), return); TestTreeItem *testCase = node->parentItem(); QTC_ASSERT(testCase->type() == Type::TestCase, return); @@ -287,7 +287,7 @@ TestTreeItem *QtTestTreeItem::find(const TestParseResult *result) const QtTestParseResult *qtResult = static_cast<const QtTestParseResult *>(result); return findChildByNameAndInheritance(qtResult->displayName, qtResult->inherited()); } - case TestFunctionOrSet: + case TestFunction: case TestDataFunction: case TestSpecialFunction: return findChildByName(result->name); @@ -306,12 +306,12 @@ TestTreeItem *QtTestTreeItem::findChild(const TestTreeItem *other) case GroupNode: return otherType == TestCase ? findChildByFile(other->filePath()) : nullptr; case TestCase: { - if (otherType != TestFunctionOrSet && otherType != TestDataFunction && otherType != TestSpecialFunction) + if (otherType != TestFunction && otherType != TestDataFunction && otherType != TestSpecialFunction) return nullptr; auto qtOther = static_cast<const QtTestTreeItem *>(other); return findChildByNameAndInheritance(other->filePath(), qtOther->inherited()); } - case TestFunctionOrSet: + case TestFunction: case TestDataFunction: case TestSpecialFunction: return otherType == TestDataTag ? findChildByName(other->name()) : nullptr; @@ -326,8 +326,8 @@ bool QtTestTreeItem::modify(const TestParseResult *result) switch (type()) { case TestCase: - return modifyTestCaseContent(result); - case TestFunctionOrSet: + return modifyTestCaseOrSuiteContent(result); + case TestFunction: case TestDataFunction: case TestSpecialFunction: return modifyTestFunctionContent(result); diff --git a/src/plugins/autotest/qtest/qttesttreeitem.h b/src/plugins/autotest/qtest/qttesttreeitem.h index 3c7f9c4bd7..97ad853580 100644 --- a/src/plugins/autotest/qtest/qttesttreeitem.h +++ b/src/plugins/autotest/qtest/qttesttreeitem.h @@ -45,7 +45,7 @@ public: TestConfiguration *debugConfiguration() const override; QList<TestConfiguration *> getAllTestConfigurations() const override; QList<TestConfiguration *> getSelectedTestConfigurations() const override; - QList<TestConfiguration *> getTestConfigurationsForFile(const Utils::FileName &fileName) const override; + QList<TestConfiguration *> getTestConfigurationsForFile(const Utils::FilePath &fileName) const override; TestTreeItem *find(const TestParseResult *result) override; TestTreeItem *findChild(const TestTreeItem *other) override; bool modify(const TestParseResult *result) override; diff --git a/src/plugins/autotest/qtest/qttestvisitors.cpp b/src/plugins/autotest/qtest/qttestvisitors.cpp index b6d6992e2d..58a69f2dbf 100644 --- a/src/plugins/autotest/qtest/qttestvisitors.cpp +++ b/src/plugins/autotest/qtest/qttestvisitors.cpp @@ -82,7 +82,7 @@ bool TestVisitor::visit(CPlusPlus::Class *symbol) else if (name.endsWith("_data")) locationAndType.m_type = TestTreeItem::TestDataFunction; else - locationAndType.m_type = TestTreeItem::TestFunctionOrSet; + locationAndType.m_type = TestTreeItem::TestFunction; locationAndType.m_inherited = m_inherited; m_privSlots.insert(className + "::" + name, locationAndType); } diff --git a/src/plugins/autotest/quick/quicktestparser.cpp b/src/plugins/autotest/quick/quicktestparser.cpp index c4f7e7963a..2542325510 100644 --- a/src/plugins/autotest/quick/quicktestparser.cpp +++ b/src/plugins/autotest/quick/quicktestparser.cpp @@ -144,7 +144,7 @@ QList<QmlJS::Document::Ptr> QuickTestParser::scanDirectoryForQuickTestQmlFiles(c // make sure even files not listed in pro file are available inside the snapshot QFutureInterface<void> future; QmlJS::PathsAndLanguages paths; - paths.maybeInsert(Utils::FileName::fromString(srcDir), QmlJS::Dialect::Qml); + paths.maybeInsert(Utils::FilePath::fromString(srcDir), QmlJS::Dialect::Qml); QmlJS::ModelManagerInterface::importScan(future, qmlJsMM->workingCopy(), paths, qmlJsMM, false /*emitDocumentChanges*/, false /*onlyTheLib*/, true /*forceRescan*/ ); @@ -276,7 +276,7 @@ void QuickTestParser::handleDirectoryChanged(const QString &directory) }); if (timestampChanged) { QmlJS::PathsAndLanguages paths; - paths.maybeInsert(Utils::FileName::fromString(directory), QmlJS::Dialect::Qml); + paths.maybeInsert(Utils::FilePath::fromString(directory), QmlJS::Dialect::Qml); QFutureInterface<void> future; QmlJS::ModelManagerInterface *qmlJsMM = QmlJS::ModelManagerInterface::instance(); QmlJS::ModelManagerInterface::importScan(future, qmlJsMM->workingCopy(), paths, qmlJsMM, diff --git a/src/plugins/autotest/quick/quicktesttreeitem.cpp b/src/plugins/autotest/quick/quicktesttreeitem.cpp index 6bc57a0e38..52d3796463 100644 --- a/src/plugins/autotest/quick/quicktesttreeitem.cpp +++ b/src/plugins/autotest/quick/quicktesttreeitem.cpp @@ -65,7 +65,7 @@ QVariant QuickTestTreeItem::data(int column, int role) const return QVariant(); case TestCase: return name().isEmpty() ? QVariant() : checked(); - case TestFunctionOrSet: + case TestFunction: return (parentItem() && !parentItem()->name().isEmpty()) ? checked() : QVariant(); default: return checked(); @@ -78,7 +78,7 @@ QVariant QuickTestTreeItem::data(int column, int role) const return true; case TestCase: return name().isEmpty(); - case TestFunctionOrSet: + case TestFunction: return parentItem() ? parentItem()->name().isEmpty() : false; default: return false; @@ -97,7 +97,7 @@ Qt::ItemFlags QuickTestTreeItem::flags(int column) const if (name().isEmpty()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable; break; - case TestFunctionOrSet: + case TestFunction: if (parentItem()->name().isEmpty()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable; break; @@ -111,7 +111,7 @@ bool QuickTestTreeItem::canProvideTestConfiguration() const switch (type()) { case TestCase: return !name().isEmpty(); - case TestFunctionOrSet: + case TestFunction: return !parentItem()->name().isEmpty(); default: return false; @@ -134,7 +134,7 @@ TestConfiguration *QuickTestTreeItem::testConfiguration() const const QString testName = name(); QStringList testFunctions; forFirstLevelChildren([&testFunctions, &testName](TestTreeItem *child) { - if (child->type() == TestTreeItem::TestFunctionOrSet) + if (child->type() == TestTreeItem::TestFunction) testFunctions << testName + "::" + child->name(); }); config = new QuickTestConfiguration; @@ -143,7 +143,7 @@ TestConfiguration *QuickTestTreeItem::testConfiguration() const config->setProject(project); break; } - case TestFunctionOrSet: { + case TestFunction: { TestTreeItem *parent = parentItem(); QStringList testFunction(parent->name() + "::" + name()); config = new QuickTestConfiguration; @@ -177,7 +177,7 @@ static void testConfigurationFromCheckState(const TestTreeItem *item, const QString testName = item->name(); QStringList testFunctions; item->forFirstLevelChildren([&testFunctions, &testName](TestTreeItem *child) { - if (child->checked() == Qt::Checked && child->type() == TestTreeItem::TestFunctionOrSet) + if (child->checked() == Qt::Checked && child->type() == TestTreeItem::TestFunction) testFunctions << testName + "::" + child->name(); }); if (foundProFiles.contains(item->proFile())) { @@ -280,7 +280,7 @@ QList<TestConfiguration *> QuickTestTreeItem::getSelectedTestConfigurations() co return result; } -QList<TestConfiguration *> QuickTestTreeItem::getTestConfigurationsForFile(const Utils::FileName &fileName) const +QList<TestConfiguration *> QuickTestTreeItem::getTestConfigurationsForFile(const Utils::FilePath &fileName) const { QList<TestConfiguration *> result; ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); @@ -290,7 +290,7 @@ QList<TestConfiguration *> QuickTestTreeItem::getTestConfigurationsForFile(const QHash<TestTreeItem *, QStringList> testFunctions; const QString &file = fileName.toString(); forAllChildren([&testFunctions, &file](TestTreeItem *node) { - if (node->type() == Type::TestFunctionOrSet && node->filePath() == file) { + if (node->type() == Type::TestFunction && node->filePath() == file) { QTC_ASSERT(node->parentItem(), return); TestTreeItem *testCase = node->parentItem(); QTC_ASSERT(testCase->type() == Type::TestCase, return); @@ -349,7 +349,7 @@ TestTreeItem *QuickTestTreeItem::findChild(const TestTreeItem *other) case GroupNode: return findChildByFileAndType(other->filePath(), otherType); case TestCase: - if (otherType != TestFunctionOrSet && otherType != TestDataFunction && otherType != TestSpecialFunction) + if (otherType != TestFunction && otherType != TestDataFunction && otherType != TestSpecialFunction) return nullptr; return name().isEmpty() ? findChildByNameAndFile(other->name(), other->filePath()) : findChildByName(other->name()); @@ -364,8 +364,8 @@ bool QuickTestTreeItem::modify(const TestParseResult *result) switch (type()) { case TestCase: - return result->name.isEmpty() ? false : modifyTestCaseContent(result); - case TestFunctionOrSet: + return result->name.isEmpty() ? false : modifyTestCaseOrSuiteContent(result); + case TestFunction: case TestDataFunction: case TestSpecialFunction: return name().isEmpty() ? modifyLineAndColumn(result) diff --git a/src/plugins/autotest/quick/quicktesttreeitem.h b/src/plugins/autotest/quick/quicktesttreeitem.h index b1d49065f3..b9f06d6a07 100644 --- a/src/plugins/autotest/quick/quicktesttreeitem.h +++ b/src/plugins/autotest/quick/quicktesttreeitem.h @@ -45,7 +45,7 @@ public: TestConfiguration *debugConfiguration() const override; QList<TestConfiguration *> getAllTestConfigurations() const override; QList<TestConfiguration *> getSelectedTestConfigurations() const override; - QList<TestConfiguration *> getTestConfigurationsForFile(const Utils::FileName &fileName) const override; + QList<TestConfiguration *> getTestConfigurationsForFile(const Utils::FilePath &fileName) const override; TestTreeItem *find(const TestParseResult *result) override; TestTreeItem *findChild(const TestTreeItem *other) override; bool modify(const TestParseResult *result) override; diff --git a/src/plugins/autotest/quick/quicktestvisitors.cpp b/src/plugins/autotest/quick/quicktestvisitors.cpp index cbc5f9b2a2..f9934e735d 100644 --- a/src/plugins/autotest/quick/quicktestvisitors.cpp +++ b/src/plugins/autotest/quick/quicktestvisitors.cpp @@ -146,7 +146,7 @@ bool TestQmlVisitor::visit(QmlJS::AST::FunctionDeclaration *ast) else if (name.endsWith("_data")) locationAndType.m_type = TestTreeItem::TestDataFunction; else - locationAndType.m_type = TestTreeItem::TestFunctionOrSet; + locationAndType.m_type = TestTreeItem::TestFunction; m_testFunctions.insert(name.toString(), locationAndType); } diff --git a/src/plugins/autotest/testcodeparser.cpp b/src/plugins/autotest/testcodeparser.cpp index 1449816fc0..c944073dc7 100644 --- a/src/plugins/autotest/testcodeparser.cpp +++ b/src/plugins/autotest/testcodeparser.cpp @@ -171,28 +171,6 @@ void TestCodeParser::updateTestTree(ITestParser *parser) scanForTests(QStringList(), parser); } -static QStringList filterFiles(const QString &projectDir, const QStringList &files) -{ - const QSharedPointer<TestSettings> &settings = AutotestPlugin::settings(); - const QSet<QString> &filters = settings->whiteListFilters.toSet(); // avoid duplicates - if (!settings->filterScan || filters.isEmpty()) - return files; - QStringList finalResult; - for (const QString &file : files) { - // apply filter only below project directory if file is part of a project - const QString &fileToProcess = file.startsWith(projectDir) - ? file.mid(projectDir.size()) - : file; - for (const QString &filter : filters) { - if (fileToProcess.contains(filter)) { - finalResult.push_back(file); - break; - } - } - } - return finalResult; -} - // used internally to indicate a parse that failed due to having triggered a parse for a file that // is not (yet) part of the CppModelManager's snapshot static bool parsingHasFailed; @@ -208,7 +186,7 @@ void TestCodeParser::onDocumentUpdated(const QString &fileName, bool isQmlFile) if (!project) return; // Quick tests: qml files aren't necessarily listed inside project files - if (!isQmlFile && !project->isKnownFile(Utils::FileName::fromString(fileName))) + if (!isQmlFile && !project->isKnownFile(Utils::FilePath::fromString(fileName))) return; scanForTests(QStringList(fileName)); @@ -343,7 +321,7 @@ void TestCodeParser::scanForTests(const QStringList &fileList, ITestParser *pars return; QStringList list; if (isFullParse) { - list = Utils::transform(project->files(Project::SourceFiles), &Utils::FileName::toString); + list = Utils::transform(project->files(Project::SourceFiles), &Utils::FilePath::toString); if (list.isEmpty()) { // at least project file should be there, but might happen if parsing current project // takes too long, especially when opening sessions holding multiple projects @@ -379,7 +357,6 @@ void TestCodeParser::scanForTests(const QStringList &fileList, ITestParser *pars m_model->markForRemoval(filePath); } - list = filterFiles(project->projectDirectory().toString(), list); if (list.isEmpty()) { if (isFullParse) { Core::MessageManager::instance()->write( diff --git a/src/plugins/autotest/testconfiguration.cpp b/src/plugins/autotest/testconfiguration.cpp index 1f9b73e77c..6f7078eefb 100644 --- a/src/plugins/autotest/testconfiguration.cpp +++ b/src/plugins/autotest/testconfiguration.cpp @@ -53,11 +53,10 @@ TestConfiguration::~TestConfiguration() m_testCases.clear(); } -static bool isLocal(RunConfiguration *runConfiguration) +static bool isLocal(Target *target) { - Target *target = runConfiguration ? runConfiguration->target() : nullptr; Kit *kit = target ? target->kit() : nullptr; - return DeviceTypeKitInformation::deviceTypeId(kit) == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE; + return DeviceTypeKitAspect::deviceTypeId(kit) == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE; } static QString ensureExeEnding(const QString& file) @@ -93,8 +92,7 @@ void TestConfiguration::completeTestInformation(ProjectExplorer::RunConfiguratio m_runnable = rc->runnable(); m_displayName = rc->displayName(); - const QString buildKey = rc->buildKey(); - BuildTargetInfo targetInfo = target->applicationTargets().buildTargetInfo(buildKey); + BuildTargetInfo targetInfo = rc->buildTargetInfo(); if (!targetInfo.targetFilePath.isEmpty()) m_runnable.executable = ensureExeEnding(targetInfo.targetFilePath.toString()); @@ -142,7 +140,7 @@ void TestConfiguration::completeTestInformation(TestRunMode runMode) const QSet<QString> buildSystemTargets = m_buildTargets; qCDebug(LOG) << "BuildSystemTargets\n " << buildSystemTargets; BuildTargetInfo targetInfo - = Utils::findOrDefault(target->applicationTargets().list, + = Utils::findOrDefault(target->applicationTargets(), [&buildSystemTargets] (const BuildTargetInfo &bti) { return buildSystemTargets.contains(bti.buildKey); }); @@ -150,7 +148,7 @@ void TestConfiguration::completeTestInformation(TestRunMode runMode) // there would be no BuildTargetInfo that could match if (targetInfo.targetFilePath.isEmpty()) { qCDebug(LOG) << "BuildTargetInfos"; - const QList<BuildTargetInfo> buildTargets = target->applicationTargets().list; + const QList<BuildTargetInfo> buildTargets = target->applicationTargets(); // if there is only one build target just use it (but be honest that we're deducing) if (buildTargets.size() == 1) { targetInfo = buildTargets.first(); @@ -184,7 +182,7 @@ void TestConfiguration::completeTestInformation(TestRunMode runMode) qCDebug(LOG) << "Iterating run configurations"; for (RunConfiguration *runConfig : target->runConfigurations()) { qCDebug(LOG) << "RunConfiguration" << runConfig->id(); - if (!isLocal(runConfig)) { // TODO add device support + if (!isLocal(target)) { // TODO add device support qCDebug(LOG) << " Skipped as not being local"; continue; } @@ -205,7 +203,7 @@ void TestConfiguration::completeTestInformation(TestRunMode runMode) m_runnable.executable = currentExecutable; m_displayName = runConfig->displayName(); if (runMode == TestRunMode::Debug || runMode == TestRunMode::DebugWithoutDeploy) - m_runConfig = new TestRunConfiguration(runConfig->target(), this); + m_runConfig = new TestRunConfiguration(target, this); break; } } @@ -220,7 +218,7 @@ void TestConfiguration::completeTestInformation(TestRunMode runMode) qCDebug(LOG) << " Fallback"; // we failed to find a valid runconfiguration - but we've got the executable already if (auto rc = target->activeRunConfiguration()) { - if (isLocal(rc)) { // FIXME for now only Desktop support + if (isLocal(target)) { // FIXME for now only Desktop support const Runnable runnable = rc->runnable(); m_runnable.environment = runnable.environment; m_deducedConfiguration = true; diff --git a/src/plugins/autotest/testconfiguration.h b/src/plugins/autotest/testconfiguration.h index 929731bce3..fcd778c8ec 100644 --- a/src/plugins/autotest/testconfiguration.h +++ b/src/plugins/autotest/testconfiguration.h @@ -28,7 +28,7 @@ #include "autotestconstants.h" #include <projectexplorer/project.h> -#include <projectexplorer/runconfiguration.h> +#include <projectexplorer/runcontrol.h> #include <utils/environment.h> #include <QFutureInterface> diff --git a/src/plugins/autotest/testeditormark.cpp b/src/plugins/autotest/testeditormark.cpp index 61e53ef4bd..29ca484c9c 100644 --- a/src/plugins/autotest/testeditormark.cpp +++ b/src/plugins/autotest/testeditormark.cpp @@ -29,7 +29,7 @@ namespace Autotest { namespace Internal { -TestEditorMark::TestEditorMark(QPersistentModelIndex item, const Utils::FileName &file, int line) +TestEditorMark::TestEditorMark(QPersistentModelIndex item, const Utils::FilePath &file, int line) : TextEditor::TextMark(file, line, Core::Id(Constants::TASK_MARK_ID)), m_item(item) { diff --git a/src/plugins/autotest/testeditormark.h b/src/plugins/autotest/testeditormark.h index c84e8efb73..458bb4f0c0 100644 --- a/src/plugins/autotest/testeditormark.h +++ b/src/plugins/autotest/testeditormark.h @@ -35,7 +35,7 @@ namespace Internal { class TestEditorMark : public TextEditor::TextMark { public: - TestEditorMark(QPersistentModelIndex item, const Utils::FileName &file, int line); + TestEditorMark(QPersistentModelIndex item, const Utils::FilePath &file, int line); bool isClickable() const override { return true; } void clicked() override; diff --git a/src/plugins/autotest/testnavigationwidget.cpp b/src/plugins/autotest/testnavigationwidget.cpp index 363fd1a3b4..9899fcc32a 100644 --- a/src/plugins/autotest/testnavigationwidget.cpp +++ b/src/plugins/autotest/testnavigationwidget.cpp @@ -197,7 +197,6 @@ QList<QToolButton *> TestNavigationWidget::createToolButtons() m_filterButton->setIcon(Utils::Icons::FILTER.icon()); m_filterButton->setToolTip(tr("Filter Test Tree")); m_filterButton->setProperty("noArrow", true); - m_filterButton->setAutoRaise(true); m_filterButton->setPopupMode(QToolButton::InstantPopup); m_filterMenu = new QMenu(m_filterButton); initializeFilterMenu(); @@ -237,7 +236,7 @@ void TestNavigationWidget::onItemActivated(const QModelIndex &index) void TestNavigationWidget::onSortClicked() { if (m_sortAlphabetically) { - m_sort->setIcon(Icons::SORT_ALPHABETICALLY.icon()); + m_sort->setIcon(Utils::Icons::SORT_ALPHABETICALLY_TOOLBAR.icon()); m_sort->setToolTip(tr("Sort Alphabetically")); m_sortFilterModel->setSortMode(TestTreeItem::Naturally); } else { diff --git a/src/plugins/autotest/testoutputreader.cpp b/src/plugins/autotest/testoutputreader.cpp index ff96d10e79..36da35543b 100644 --- a/src/plugins/autotest/testoutputreader.cpp +++ b/src/plugins/autotest/testoutputreader.cpp @@ -74,11 +74,11 @@ void TestOutputReader::reportCrash() { TestResultPtr result = createDefaultResult(); result->setDescription(tr("Test executable crashed.")); - result->setResult(Result::MessageFatal); + result->setResult(ResultType::MessageFatal); m_futureInterface.reportResult(result); } -void TestOutputReader::createAndReportResult(const QString &message, Result::Type type) +void TestOutputReader::createAndReportResult(const QString &message, ResultType type) { TestResultPtr result = createDefaultResult(); result->setDescription(message); diff --git a/src/plugins/autotest/testoutputreader.h b/src/plugins/autotest/testoutputreader.h index 7c1f7d0f13..87216a2570 100644 --- a/src/plugins/autotest/testoutputreader.h +++ b/src/plugins/autotest/testoutputreader.h @@ -45,8 +45,11 @@ public: void processOutput(const QByteArray &output); virtual void processStdError(const QByteArray &outputLineWithNewLine); void reportCrash(); - void createAndReportResult(const QString &message, Result::Type type); + void createAndReportResult(const QString &message, ResultType type); bool hadValidOutput() const { return m_hadValidOutput; } + int disabledTests() const { return m_disabled; } + bool hasSummary() const { return !m_summary.isEmpty(); } + QHash<ResultType, int> summary() const { return m_summary; } void setId(const QString &id) { m_id = id; } QString id() const { return m_id; } @@ -63,6 +66,8 @@ protected: QProcess *m_testApplication; // not owned QString m_buildDir; QString m_id; + QHash<ResultType, int> m_summary; + int m_disabled = -1; private: bool m_hadValidOutput = false; }; diff --git a/src/plugins/autotest/testresult.cpp b/src/plugins/autotest/testresult.cpp index ea4261a453..e9ff367ef9 100644 --- a/src/plugins/autotest/testresult.cpp +++ b/src/plugins/autotest/testresult.cpp @@ -31,22 +31,6 @@ namespace Autotest { namespace Internal { -FaultyTestResult::FaultyTestResult(Result::Type result, const QString &description) -{ - setResult(result); - setDescription(description); -} - -TestResult::TestResult() - : TestResult(QString()) -{ -} - -TestResult::TestResult(const QString &name) - : m_name(name) -{ -} - TestResult::TestResult(const QString &id, const QString &name) : m_id(id) , m_name(name) @@ -55,7 +39,7 @@ TestResult::TestResult(const QString &id, const QString &name) const QString TestResult::outputString(bool selected) const { - if (m_result == Result::Application) + if (m_result == ResultType::Application) return m_id; return selected ? m_description : m_description.split('\n').first(); } @@ -65,138 +49,127 @@ const TestTreeItem *TestResult::findTestTreeItem() const return nullptr; } -Result::Type TestResult::resultFromString(const QString &resultString) +ResultType TestResult::resultFromString(const QString &resultString) { if (resultString == "pass") - return Result::Pass; + return ResultType::Pass; if (resultString == "fail" || resultString == "fail!") - return Result::Fail; + return ResultType::Fail; if (resultString == "xfail") - return Result::ExpectedFail; + return ResultType::ExpectedFail; if (resultString == "xpass") - return Result::UnexpectedPass; + return ResultType::UnexpectedPass; if (resultString == "skip") - return Result::Skip; + return ResultType::Skip; if (resultString == "result") - return Result::Benchmark; + return ResultType::Benchmark; if (resultString == "qdebug") - return Result::MessageDebug; + return ResultType::MessageDebug; if (resultString == "qinfo" || resultString == "info") - return Result::MessageInfo; + return ResultType::MessageInfo; if (resultString == "warn" || resultString == "qwarn" || resultString == "warning") - return Result::MessageWarn; + return ResultType::MessageWarn; if (resultString == "qfatal") - return Result::MessageFatal; + return ResultType::MessageFatal; if ((resultString == "system") || (resultString == "qsystem")) - return Result::MessageSystem; + return ResultType::MessageSystem; if (resultString == "bpass") - return Result::BlacklistedPass; + return ResultType::BlacklistedPass; if (resultString == "bfail") - return Result::BlacklistedFail; + return ResultType::BlacklistedFail; if (resultString == "bxpass") - return Result::BlacklistedXPass; + return ResultType::BlacklistedXPass; if (resultString == "bxfail") - return Result::BlacklistedXFail; + return ResultType::BlacklistedXFail; qDebug("Unexpected test result: %s", qPrintable(resultString)); - return Result::Invalid; + return ResultType::Invalid; } -Result::Type TestResult::toResultType(int rt) +ResultType TestResult::toResultType(int rt) { - if (rt < Result::FIRST_TYPE || rt > Result::LAST_TYPE) - return Result::Invalid; + if (rt < int(ResultType::FIRST_TYPE) || rt > int(ResultType::LAST_TYPE)) + return ResultType::Invalid; - return Result::Type(rt); + return ResultType(rt); } -QString TestResult::resultToString(const Result::Type type) +QString TestResult::resultToString(const ResultType type) { switch (type) { - case Result::Pass: - case Result::MessageTestCaseSuccess: - case Result::MessageTestCaseSuccessWarn: + case ResultType::Pass: return QString("PASS"); - case Result::Fail: - case Result::MessageTestCaseFail: - case Result::MessageTestCaseFailWarn: + case ResultType::Fail: return QString("FAIL"); - case Result::ExpectedFail: + case ResultType::ExpectedFail: return QString("XFAIL"); - case Result::UnexpectedPass: + case ResultType::UnexpectedPass: return QString("XPASS"); - case Result::Skip: + case ResultType::Skip: return QString("SKIP"); - case Result::Benchmark: + case ResultType::Benchmark: return QString("BENCH"); - case Result::MessageDebug: + case ResultType::MessageDebug: return QString("DEBUG"); - case Result::MessageInfo: + case ResultType::MessageInfo: return QString("INFO"); - case Result::MessageWarn: + case ResultType::MessageWarn: return QString("WARN"); - case Result::MessageFatal: + case ResultType::MessageFatal: return QString("FATAL"); - case Result::MessageSystem: + case ResultType::MessageSystem: return QString("SYSTEM"); - case Result::BlacklistedPass: + case ResultType::BlacklistedPass: return QString("BPASS"); - case Result::BlacklistedFail: + case ResultType::BlacklistedFail: return QString("BFAIL"); - case Result::BlacklistedXPass: + case ResultType::BlacklistedXPass: return QString("BXPASS"); - case Result::BlacklistedXFail: + case ResultType::BlacklistedXFail: return QString("BXFAIL"); - case Result::MessageLocation: - case Result::Application: + case ResultType::MessageLocation: + case ResultType::Application: return QString(); default: - if (type >= Result::INTERNAL_MESSAGES_BEGIN && type <= Result::INTERNAL_MESSAGES_END) + if (type >= ResultType::INTERNAL_MESSAGES_BEGIN && type <= ResultType::INTERNAL_MESSAGES_END) return QString(); return QString("UNKNOWN"); } } -QColor TestResult::colorForType(const Result::Type type) +QColor TestResult::colorForType(const ResultType type) { - if (type >= Result::INTERNAL_MESSAGES_BEGIN && type <= Result::INTERNAL_MESSAGES_END) + if (type >= ResultType::INTERNAL_MESSAGES_BEGIN && type <= ResultType::INTERNAL_MESSAGES_END) return QColor("transparent"); Utils::Theme *creatorTheme = Utils::creatorTheme(); switch (type) { - case Result::Pass: + case ResultType::Pass: return creatorTheme->color(Utils::Theme::OutputPanes_TestPassTextColor); - case Result::Fail: + case ResultType::Fail: return creatorTheme->color(Utils::Theme::OutputPanes_TestFailTextColor); - case Result::ExpectedFail: + case ResultType::ExpectedFail: return creatorTheme->color(Utils::Theme::OutputPanes_TestXFailTextColor); - case Result::UnexpectedPass: + case ResultType::UnexpectedPass: return creatorTheme->color(Utils::Theme::OutputPanes_TestXPassTextColor); - case Result::Skip: + case ResultType::Skip: return creatorTheme->color(Utils::Theme::OutputPanes_TestSkipTextColor); - case Result::MessageDebug: - case Result::MessageInfo: + case ResultType::MessageDebug: + case ResultType::MessageInfo: return creatorTheme->color(Utils::Theme::OutputPanes_TestDebugTextColor); - case Result::MessageWarn: + case ResultType::MessageWarn: return creatorTheme->color(Utils::Theme::OutputPanes_TestWarnTextColor); - case Result::MessageFatal: - case Result::MessageSystem: + case ResultType::MessageFatal: + case ResultType::MessageSystem: return creatorTheme->color(Utils::Theme::OutputPanes_TestFatalTextColor); - case Result::BlacklistedPass: - case Result::BlacklistedFail: - case Result::BlacklistedXPass: - case Result::BlacklistedXFail: + case ResultType::BlacklistedPass: + case ResultType::BlacklistedFail: + case ResultType::BlacklistedXPass: + case ResultType::BlacklistedXFail: default: return creatorTheme->color(Utils::Theme::OutputPanes_StdOutTextColor); } } -bool TestResult::isMessageCaseStart(const Result::Type type) -{ - return type == Result::MessageTestCaseStart || type == Result::MessageTestCaseSuccess - || type == Result::MessageTestCaseFail || type == Result::MessageTestCaseSuccessWarn - || type == Result::MessageTestCaseFailWarn || type == Result::MessageIntermediate; -} - bool TestResult::isDirectParentOf(const TestResult *other, bool * /*needsIntermediate*/) const { QTC_ASSERT(other, return false); diff --git a/src/plugins/autotest/testresult.h b/src/plugins/autotest/testresult.h index a1e1d2e7bf..84fc2c144a 100644 --- a/src/plugins/autotest/testresult.h +++ b/src/plugins/autotest/testresult.h @@ -37,8 +37,8 @@ namespace Internal { class TestTreeItem; -namespace Result{ -enum Type { +enum class ResultType { + // result types (have icon, color, short text) Pass, FIRST_TYPE = Pass, Fail, ExpectedFail, @@ -48,37 +48,40 @@ enum Type { BlacklistedFail, BlacklistedXPass, BlacklistedXFail, + + // special (message) types (have icon, color, short text) Benchmark, MessageDebug, MessageInfo, MessageWarn, MessageFatal, MessageSystem, - MessageLocation, + // special message - get's icon (but no color/short text) from parent + MessageLocation, + // anything below is an internal message (or a pure message without icon) MessageInternal, INTERNAL_MESSAGES_BEGIN = MessageInternal, - MessageDisabledTests, - MessageTestCaseStart, - MessageTestCaseSuccess, - MessageTestCaseSuccessWarn, - MessageTestCaseFail, - MessageTestCaseFailWarn, - MessageTestCaseEnd, - MessageIntermediate, + // start item (get icon/short text depending on children) + TestStart, + // usually no icon/short text - more or less an indicator (and can contain test duration) + TestEnd, + // special global (temporary) message MessageCurrentTest, INTERNAL_MESSAGES_END = MessageCurrentTest, - Application, - - Invalid, + Application, // special.. not to be used outside of testresultmodel + Invalid, // indicator for unknown result items LAST_TYPE = Invalid }; + +inline uint qHash(const ResultType &result) +{ + return QT_PREPEND_NAMESPACE(qHash(int(result))); } class TestResult { public: - TestResult(); - explicit TestResult(const QString &name); + TestResult() = default; TestResult(const QString &id, const QString &name); virtual ~TestResult() {} @@ -87,7 +90,7 @@ public: QString id() const { return m_id; } QString name() const { return m_name; } - Result::Type result() const { return m_result; } + ResultType result() const { return m_result; } QString description() const { return m_description; } QString fileName() const { return m_file; } int line() const { return m_line; } @@ -95,22 +98,20 @@ public: void setDescription(const QString &description) { m_description = description; } void setFileName(const QString &fileName) { m_file = fileName; } void setLine(int line) { m_line = line; } - void setResult(Result::Type type) { m_result = type; } + void setResult(ResultType type) { m_result = type; } - static Result::Type resultFromString(const QString &resultString); - static Result::Type toResultType(int rt); - static QString resultToString(const Result::Type type); - static QColor colorForType(const Result::Type type); - static bool isMessageCaseStart(const Result::Type type); + static ResultType resultFromString(const QString &resultString); + static ResultType toResultType(int rt); + static QString resultToString(const ResultType type); + static QColor colorForType(const ResultType type); virtual bool isDirectParentOf(const TestResult *other, bool *needsIntermediate) const; virtual bool isIntermediateFor(const TestResult *other) const; virtual TestResult *createIntermediateResultFor(const TestResult *other); - private: QString m_id; QString m_name; - Result::Type m_result = Result::Invalid; + ResultType m_result = ResultType::Invalid; // the real result.. QString m_description; QString m_file; int m_line = 0; @@ -118,14 +119,8 @@ private: using TestResultPtr = QSharedPointer<TestResult>; -class FaultyTestResult : public TestResult -{ -public: - FaultyTestResult(Result::Type result, const QString &description); -}; - } // namespace Internal } // namespace Autotest Q_DECLARE_METATYPE(Autotest::Internal::TestResult) -Q_DECLARE_METATYPE(Autotest::Internal::Result::Type) +Q_DECLARE_METATYPE(Autotest::Internal::ResultType) diff --git a/src/plugins/autotest/testresultdelegate.cpp b/src/plugins/autotest/testresultdelegate.cpp index d70b6fa39e..f5486149af 100644 --- a/src/plugins/autotest/testresultdelegate.cpp +++ b/src/plugins/autotest/testresultdelegate.cpp @@ -40,12 +40,6 @@ namespace Internal { constexpr int outputLimit = 100000; -static bool isSummaryItem(Result::Type type) -{ - return type == Result::MessageTestCaseSuccess || type == Result::MessageTestCaseSuccessWarn - || type == Result::MessageTestCaseFail || type == Result::MessageTestCaseFailWarn; -} - TestResultDelegate::TestResultDelegate(QObject *parent) : QStyledItemDelegate(parent) { @@ -89,12 +83,14 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->drawPixmap(positions.left(), positions.top(), icon.pixmap(window, QSize(positions.iconSize(), positions.iconSize()))); - QString typeStr = TestResult::resultToString(testResult->result()); + TestResultItem *item = resultFilterModel->itemForIndex(index); + QTC_ASSERT(item, painter->restore(); return); + const QString typeStr = item->resultString(); if (selected) { painter->drawText(positions.typeAreaLeft(), positions.top() + fm.ascent(), typeStr); } else { QPen tmp = painter->pen(); - if (isSummaryItem(testResult->result())) + if (testResult->result() == ResultType::TestStart) painter->setPen(opt.palette.mid().color()); else painter->setPen(TestResult::colorForType(testResult->result())); diff --git a/src/plugins/autotest/testresultdelegate.h b/src/plugins/autotest/testresultdelegate.h index 5921ebdc92..a3b2cb7c5f 100644 --- a/src/plugins/autotest/testresultdelegate.h +++ b/src/plugins/autotest/testresultdelegate.h @@ -65,7 +65,7 @@ private: m_maxFileLength = srcModel->maxWidthOfFileName(options.font); m_maxLineLength = srcModel->maxWidthOfLineNumber(options.font); m_realFileLength = m_maxFileLength; - m_typeAreaWidth = QFontMetrics(options.font).width("XXXXXXXX"); + m_typeAreaWidth = QFontMetrics(options.font).horizontalAdvance("XXXXXXXX"); m_indentation = options.widget ? options.widget->style()->pixelMetric( QStyle::PM_TreeViewIndentation, &options) : 0; diff --git a/src/plugins/autotest/testresultmodel.cpp b/src/plugins/autotest/testresultmodel.cpp index deb6e1dfae..a3c6ec28a8 100644 --- a/src/plugins/autotest/testresultmodel.cpp +++ b/src/plugins/autotest/testresultmodel.cpp @@ -23,10 +23,11 @@ ** ****************************************************************************/ +#include "testresultmodel.h" #include "autotesticons.h" #include "autotestplugin.h" #include "testresultdelegate.h" -#include "testresultmodel.h" +#include "testrunner.h" #include "testsettings.h" #include <projectexplorer/projectexplorericons.h> @@ -45,7 +46,7 @@ TestResultItem::TestResultItem(const TestResultPtr &testResult) { } -static QIcon testResultIcon(Result::Type result) { +static QIcon testResultIcon(ResultType result) { const static QIcon icons[] = { Icons::RESULT_PASS.icon(), Icons::RESULT_FAIL.icon(), @@ -62,29 +63,27 @@ static QIcon testResultIcon(Result::Type result) { Icons::RESULT_MESSAGEWARN.icon(), Icons::RESULT_MESSAGEFATAL.icon(), Icons::RESULT_MESSAGEFATAL.icon(), // System gets same handling as Fatal for now - QIcon(), - Icons::RESULT_MESSAGEPASSWARN.icon(), - Icons::RESULT_MESSAGEFAILWARN.icon(), ProjectExplorer::Icons::DESKTOP_DEVICE.icon(), // for now }; // provide an icon for unknown?? - if (result < 0 || result >= Result::MessageInternal) { + if (result < ResultType::FIRST_TYPE || result >= ResultType::MessageInternal) { switch (result) { - case Result::MessageTestCaseSuccess: - return icons[Result::Pass]; - case Result::MessageTestCaseFail: - return icons[Result::Fail]; - case Result::MessageTestCaseSuccessWarn: - return icons[16]; - case Result::MessageTestCaseFailWarn: - return icons[17]; - case Result::Application: - return icons[18]; + case ResultType::Application: + return icons[15]; default: return QIcon(); } } - return icons[result]; + return icons[int(result)]; +} + +static QIcon testSummaryIcon(const Utils::optional<TestResultItem::SummaryEvaluation> &summary) +{ + if (!summary) + return QIcon(); + if (summary->failed) + return summary->warnings ? Icons::RESULT_MESSAGEFAILWARN.icon() : Icons::RESULT_FAIL.icon(); + return summary->warnings ? Icons::RESULT_MESSAGEPASSWARN.icon() : Icons::RESULT_PASS.icon(); } QVariant TestResultItem::data(int column, int role) const @@ -93,9 +92,11 @@ QVariant TestResultItem::data(int column, int role) const case Qt::DecorationRole: { if (!m_testResult) return QVariant(); - const Result::Type result = m_testResult->result(); - if (result == Result::MessageLocation && parent()) + const ResultType result = m_testResult->result(); + if (result == ResultType::MessageLocation && parent()) return parent()->data(column, role); + if (result == ResultType::TestStart) + return testSummaryIcon(m_summaryResult); return testResultIcon(result); } case Qt::DisplayRole: @@ -112,50 +113,72 @@ void TestResultItem::updateDescription(const QString &description) m_testResult->setDescription(description); } -void TestResultItem::updateResult(bool &changed, Result::Type addedChildType) +static bool isSignificant(ResultType type) +{ + switch (type) { + case ResultType::Benchmark: + case ResultType::MessageInfo: + case ResultType::MessageInternal: + case ResultType::TestEnd: + return false; + case ResultType::MessageLocation: + case ResultType::MessageCurrentTest: + case ResultType::Application: + case ResultType::Invalid: + QTC_ASSERT_STRING("Got unexpedted type in isSignificant check"); + return false; + default: + return true; + } +} + +void TestResultItem::updateResult(bool &changed, ResultType addedChildType, + const Utils::optional<SummaryEvaluation> &summary) { changed = false; - const Result::Type old = m_testResult->result(); - if (old == Result::MessageTestCaseFailWarn) // can't become worse + if (m_testResult->result() != ResultType::TestStart) return; - if (!TestResult::isMessageCaseStart(old)) + + if (!isSignificant(addedChildType) || (addedChildType == ResultType::TestStart && !summary)) return; - Result::Type newResult = old; + if (m_summaryResult.has_value() && m_summaryResult->failed && m_summaryResult->warnings) + return; // can't become worse + + SummaryEvaluation newResult = m_summaryResult.value_or(SummaryEvaluation()); switch (addedChildType) { - case Result::Fail: - case Result::MessageFatal: - case Result::UnexpectedPass: - case Result::MessageTestCaseFail: - newResult = (old == Result::MessageTestCaseSuccessWarn) ? Result::MessageTestCaseFailWarn - : Result::MessageTestCaseFail; + case ResultType::Fail: + case ResultType::MessageFatal: + case ResultType::UnexpectedPass: + if (newResult.failed) + return; + newResult.failed = true; break; - case Result::MessageTestCaseFailWarn: - newResult = Result::MessageTestCaseFailWarn; + case ResultType::ExpectedFail: + case ResultType::MessageWarn: + case ResultType::MessageSystem: + case ResultType::Skip: + case ResultType::BlacklistedFail: + case ResultType::BlacklistedPass: + case ResultType::BlacklistedXFail: + case ResultType::BlacklistedXPass: + if (newResult.warnings) + return; + newResult.warnings = true; break; - case Result::ExpectedFail: - case Result::MessageWarn: - case Result::MessageSystem: - case Result::Skip: - case Result::BlacklistedFail: - case Result::BlacklistedPass: - case Result::BlacklistedXFail: - case Result::BlacklistedXPass: - case Result::MessageTestCaseSuccessWarn: - newResult = (old == Result::MessageTestCaseFail) ? Result::MessageTestCaseFailWarn - : Result::MessageTestCaseSuccessWarn; - break; - case Result::Pass: - case Result::MessageTestCaseSuccess: - newResult = (old == Result::MessageIntermediate || old == Result::MessageTestCaseStart) - ? Result::MessageTestCaseSuccess : old; + case ResultType::TestStart: + if (summary) { + newResult.failed |= summary->failed; + newResult.warnings |= summary->warnings; + } break; default: break; } - changed = old != newResult; + changed = !m_summaryResult.has_value() || m_summaryResult.value() != newResult; + if (changed) - m_testResult->setResult(newResult); + m_summaryResult.emplace(newResult); } TestResultItem *TestResultItem::intermediateFor(const TestResultItem *item) const @@ -165,7 +188,7 @@ TestResultItem *TestResultItem::intermediateFor(const TestResultItem *item) cons for (int row = childCount() - 1; row >= 0; --row) { TestResultItem *child = childAt(row); const TestResult *testResult = child->testResult(); - if (testResult->result() != Result::MessageIntermediate) + if (testResult->result() != ResultType::TestStart) continue; if (testResult->isIntermediateFor(otherResult)) return child; @@ -177,17 +200,30 @@ TestResultItem *TestResultItem::createAndAddIntermediateFor(const TestResultItem { TestResultPtr result(m_testResult->createIntermediateResultFor(child->testResult())); QTC_ASSERT(!result.isNull(), return nullptr); - result->setResult(Result::MessageIntermediate); + result->setResult(ResultType::TestStart); TestResultItem *intermediate = new TestResultItem(result); appendChild(intermediate); return intermediate; } +QString TestResultItem::resultString() const +{ + if (testResult()->result() != ResultType::TestStart) + return TestResult::resultToString(testResult()->result()); + if (!m_summaryResult) + return QString(); + return m_summaryResult->failed ? QString("FAIL") : QString("PASS"); +} + /********************************* TestResultModel *****************************************/ TestResultModel::TestResultModel(QObject *parent) : Utils::TreeModel<TestResultItem>(new TestResultItem(TestResultPtr()), parent) { + connect(TestRunner::instance(), &TestRunner::reportSummary, + this, [this](const QString &id, const QHash<ResultType, int> &summary){ + m_reportedSummary.insert(id, summary); + }); } void TestResultModel::updateParent(const TestResultItem *item) @@ -198,7 +234,7 @@ void TestResultModel::updateParent(const TestResultItem *item) if (parentItem == rootItem()) // do not update invisible root item return; bool changed = false; - parentItem->updateResult(changed, item->testResult()->result()); + parentItem->updateResult(changed, item->testResult()->result(), item->summaryResult()); if (!changed) return; emit dataChanged(parentItem->index(), parentItem->index()); @@ -208,12 +244,12 @@ void TestResultModel::updateParent(const TestResultItem *item) void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoExpand) { const int lastRow = rootItem()->childCount() - 1; - if (testResult->result() == Result::MessageCurrentTest) { + if (testResult->result() == ResultType::MessageCurrentTest) { // MessageCurrentTest should always be the last top level item if (lastRow >= 0) { TestResultItem *current = rootItem()->childAt(lastRow); const TestResult *result = current->testResult(); - if (result && result->result() == Result::MessageCurrentTest) { + if (result && result->result() == ResultType::MessageCurrentTest) { current->updateDescription(testResult->description()); emit dataChanged(current->index(), current->index()); return; @@ -224,12 +260,9 @@ void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoEx return; } - if (testResult->result() == Result::MessageDisabledTests) - m_disabled += testResult->line(); - m_testResultCount[testResult->result()]++; + m_testResultCount[testResult->id()][testResult->result()]++; TestResultItem *newItem = new TestResultItem(testResult); - TestResultItem *root = nullptr; if (AutotestPlugin::settings()->displayApplication) { const QString application = testResult->id(); @@ -241,7 +274,7 @@ void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoEx if (!root) { TestResult *tmpAppResult = new TestResult(application, application); - tmpAppResult->setResult(Result::Application); + tmpAppResult->setResult(ResultType::Application); root = new TestResultItem(TestResultPtr(tmpAppResult)); if (lastRow >= 0) rootItem()->insertChild(lastRow, root); @@ -262,7 +295,7 @@ void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoEx if (lastRow >= 0) { TestResultItem *current = rootItem()->childAt(lastRow); const TestResult *result = current->testResult(); - if (result && result->result() == Result::MessageCurrentTest) { + if (result && result->result() == ResultType::MessageCurrentTest) { rootItem()->insertChild(current->index().row(), newItem); return; } @@ -275,7 +308,7 @@ void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoEx void TestResultModel::removeCurrentTestMessage() { TestResultItem *currentMessageItem = rootItem()->findFirstLevelChild([](TestResultItem *it) { - return (it->testResult()->result() == Result::MessageCurrentTest); + return (it->testResult()->result() == ResultType::MessageCurrentTest); }); if (currentMessageItem) destroyItem(currentMessageItem); @@ -285,6 +318,7 @@ void TestResultModel::clearTestResults() { clear(); m_testResultCount.clear(); + m_reportedSummary.clear(); m_disabled = 0; m_fileNames.clear(); m_maxWidthOfFileName = 0; @@ -305,7 +339,7 @@ void TestResultModel::recalculateMaxWidthOfFileName(const QFont &font) m_maxWidthOfFileName = 0; for (const QString &fileName : m_fileNames) { int pos = fileName.lastIndexOf('/'); - m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(fileName.mid(pos + 1))); + m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.horizontalAdvance(fileName.mid(pos + 1))); } } @@ -313,7 +347,7 @@ void TestResultModel::addFileName(const QString &fileName) { const QFontMetrics fm(m_measurementFont); int pos = fileName.lastIndexOf('/'); - m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(fileName.mid(pos + 1))); + m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.horizontalAdvance(fileName.mid(pos + 1))); m_fileNames.insert(fileName); } @@ -329,11 +363,26 @@ int TestResultModel::maxWidthOfLineNumber(const QFont &font) if (m_widthOfLineNumber == 0 || font != m_measurementFont) { QFontMetrics fm(font); m_measurementFont = font; - m_widthOfLineNumber = fm.width("88888"); + m_widthOfLineNumber = fm.horizontalAdvance("88888"); } return m_widthOfLineNumber; } +int TestResultModel::resultTypeCount(ResultType type) const +{ + int result = 0; + + for (auto resultsForId : m_testResultCount.values()) + result += resultsForId.value(type, 0); + + for (auto id : m_reportedSummary.keys()) { + if (int counted = m_testResultCount.value(id).value(type)) + result -= counted; + result += m_reportedSummary[id].value(type); + } + return result; +} + TestResultItem *TestResultModel::findParentItemFor(const TestResultItem *item, const TestResultItem *startItem) const { @@ -387,42 +436,39 @@ TestResultFilterModel::TestResultFilterModel(TestResultModel *sourceModel, QObje void TestResultFilterModel::enableAllResultTypes(bool enabled) { if (enabled) { - m_enabled << Result::Pass << Result::Fail << Result::ExpectedFail - << Result::UnexpectedPass << Result::Skip << Result::MessageDebug - << Result::MessageWarn << Result::MessageInternal << Result::MessageLocation - << Result::MessageFatal << Result::Invalid << Result::BlacklistedPass - << Result::BlacklistedFail << Result::BlacklistedXFail << Result::BlacklistedXPass - << Result::Benchmark << Result::MessageIntermediate - << Result::MessageCurrentTest << Result::MessageTestCaseStart - << Result::MessageTestCaseSuccess << Result::MessageTestCaseSuccessWarn - << Result::MessageTestCaseFail << Result::MessageTestCaseFailWarn - << Result::MessageTestCaseEnd - << Result::MessageInfo << Result::MessageSystem << Result::Application; + m_enabled << ResultType::Pass << ResultType::Fail << ResultType::ExpectedFail + << ResultType::UnexpectedPass << ResultType::Skip << ResultType::MessageDebug + << ResultType::MessageWarn << ResultType::MessageInternal << ResultType::MessageLocation + << ResultType::MessageFatal << ResultType::Invalid << ResultType::BlacklistedPass + << ResultType::BlacklistedFail << ResultType::BlacklistedXFail << ResultType::BlacklistedXPass + << ResultType::Benchmark + << ResultType::MessageCurrentTest << ResultType::TestStart << ResultType::TestEnd + << ResultType::MessageInfo << ResultType::MessageSystem << ResultType::Application; } else { m_enabled.clear(); - m_enabled << Result::MessageFatal << Result::MessageSystem; + m_enabled << ResultType::MessageFatal << ResultType::MessageSystem; } invalidateFilter(); } -void TestResultFilterModel::toggleTestResultType(Result::Type type) +void TestResultFilterModel::toggleTestResultType(ResultType type) { if (m_enabled.contains(type)) { m_enabled.remove(type); - if (type == Result::MessageInternal) - m_enabled.remove(Result::MessageTestCaseEnd); - if (type == Result::MessageDebug) - m_enabled.remove(Result::MessageInfo); - if (type == Result::MessageWarn) - m_enabled.remove(Result::MessageSystem); + if (type == ResultType::MessageInternal) + m_enabled.remove(ResultType::TestEnd); + if (type == ResultType::MessageDebug) + m_enabled.remove(ResultType::MessageInfo); + if (type == ResultType::MessageWarn) + m_enabled.remove(ResultType::MessageSystem); } else { m_enabled.insert(type); - if (type == Result::MessageInternal) - m_enabled.insert(Result::MessageTestCaseEnd); - if (type == Result::MessageDebug) - m_enabled.insert(Result::MessageInfo); - if (type == Result::MessageWarn) - m_enabled.insert(Result::MessageSystem); + if (type == ResultType::MessageInternal) + m_enabled.insert(ResultType::TestEnd); + if (type == ResultType::MessageDebug) + m_enabled.insert(ResultType::MessageInfo); + if (type == ResultType::MessageWarn) + m_enabled.insert(ResultType::MessageSystem); } invalidateFilter(); } @@ -442,38 +488,41 @@ const TestResult *TestResultFilterModel::testResult(const QModelIndex &index) co return m_sourceModel->testResult(mapToSource(index)); } +TestResultItem *TestResultFilterModel::itemForIndex(const QModelIndex &index) const +{ + return index.isValid() ? m_sourceModel->itemForIndex(mapToSource(index)) : nullptr; +} + bool TestResultFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex index = m_sourceModel->index(sourceRow, 0, sourceParent); if (!index.isValid()) return false; - Result::Type resultType = m_sourceModel->testResult(index)->result(); - switch (resultType) { - case Result::MessageTestCaseSuccess: - return m_enabled.contains(Result::Pass); - case Result::MessageTestCaseFail: - case Result::MessageTestCaseSuccessWarn: - case Result::MessageTestCaseFailWarn: + ResultType resultType = m_sourceModel->testResult(index)->result(); + if (resultType == ResultType::TestStart) { + TestResultItem *item = m_sourceModel->itemForIndex(index); + QTC_ASSERT(item, return false); + if (!item->summaryResult()) + return true; return acceptTestCaseResult(index); - default: - return m_enabled.contains(resultType); } + return m_enabled.contains(resultType); } bool TestResultFilterModel::acceptTestCaseResult(const QModelIndex &srcIndex) const { for (int row = 0, count = m_sourceModel->rowCount(srcIndex); row < count; ++row) { const QModelIndex &child = m_sourceModel->index(row, 0, srcIndex); - Result::Type type = m_sourceModel->testResult(child)->result(); - if (type == Result::MessageTestCaseSuccess) - type = Result::Pass; - if (type == Result::MessageTestCaseFail || type == Result::MessageTestCaseFailWarn - || type == Result::MessageTestCaseSuccessWarn) { + TestResultItem *item = m_sourceModel->itemForIndex(child); + ResultType type = item->testResult()->result(); + + if (type == ResultType::TestStart) { + if (!item->summaryResult()) + return true; if (acceptTestCaseResult(child)) return true; - } else if (m_enabled.contains(type)) { + } else if (m_enabled.contains(type)) return true; - } } return false; } diff --git a/src/plugins/autotest/testresultmodel.h b/src/plugins/autotest/testresultmodel.h index c240b434bb..6370296ac0 100644 --- a/src/plugins/autotest/testresultmodel.h +++ b/src/plugins/autotest/testresultmodel.h @@ -32,6 +32,7 @@ #include <QFont> #include <QSet> +#include <utils/optional.h> #include <utils/treemodel.h> namespace Autotest { @@ -44,13 +45,29 @@ public: QVariant data(int column, int role) const override; const TestResult *testResult() const { return m_testResult.data(); } void updateDescription(const QString &description); - void updateResult(bool &changed, Result::Type addedChildType); + + struct SummaryEvaluation + { + bool failed = false; + bool warnings = false; + + bool operator==(const SummaryEvaluation &other) const + { return failed == other.failed && warnings == other.warnings; } + bool operator!=(const SummaryEvaluation &other) const + { return !(*this == other); } + }; + + void updateResult(bool &changed, ResultType addedChildType, + const Utils::optional<SummaryEvaluation> &summary); TestResultItem *intermediateFor(const TestResultItem *item) const; TestResultItem *createAndAddIntermediateFor(const TestResultItem *child); + QString resultString() const; + Utils::optional<SummaryEvaluation> summaryResult() const { return m_summaryResult; } private: TestResultPtr m_testResult; + Utils::optional<SummaryEvaluation> m_summaryResult; }; class TestResultModel : public Utils::TreeModel<TestResultItem> @@ -67,8 +84,9 @@ public: int maxWidthOfFileName(const QFont &font); int maxWidthOfLineNumber(const QFont &font); - int resultTypeCount(Result::Type type) const { return m_testResultCount.value(type, 0); } + int resultTypeCount(ResultType type) const; int disabledTests() const { return m_disabled; } + void raiseDisabledTests(int amount) { m_disabled += amount; } private: void recalculateMaxWidthOfFileName(const QFont &font); @@ -76,7 +94,8 @@ private: TestResultItem *findParentItemFor(const TestResultItem *item, const TestResultItem *startItem = nullptr) const; void updateParent(const TestResultItem *item); - QMap<Result::Type, int> m_testResultCount; + QHash<QString, QMap<ResultType, int>> m_testResultCount; + QHash<QString, QHash<ResultType, int>> m_reportedSummary; int m_widthOfLineNumber = 0; int m_maxWidthOfFileName = 0; int m_disabled = 0; @@ -91,10 +110,11 @@ public: explicit TestResultFilterModel(TestResultModel *sourceModel, QObject *parent = nullptr); void enableAllResultTypes(bool enabled); - void toggleTestResultType(Result::Type type); + void toggleTestResultType(ResultType type); void clearTestResults(); bool hasResults(); const TestResult *testResult(const QModelIndex &index) const; + TestResultItem *itemForIndex(const QModelIndex &index) const; protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; @@ -102,7 +122,7 @@ protected: private: bool acceptTestCaseResult(const QModelIndex &srcIndex) const; TestResultModel *m_sourceModel; - QSet<Result::Type> m_enabled; + QSet<ResultType> m_enabled; }; } // namespace Internal diff --git a/src/plugins/autotest/testresultspane.cpp b/src/plugins/autotest/testresultspane.cpp index 24d2ff0383..468cc9e32c 100644 --- a/src/plugins/autotest/testresultspane.cpp +++ b/src/plugins/autotest/testresultspane.cpp @@ -157,6 +157,8 @@ TestResultsPane::TestResultsPane(QObject *parent) : this, &TestResultsPane::onTestRunFinished); connect(TestRunner::instance(), &TestRunner::testResultReady, this, &TestResultsPane::addTestResult); + connect(TestRunner::instance(), &TestRunner::hadDisabledTests, + m_model, &TestResultModel::raiseDisabledTests); } void TestResultsPane::createToolButtons() @@ -192,7 +194,6 @@ void TestResultsPane::createToolButtons() m_filterButton->setIcon(Utils::Icons::FILTER.icon()); m_filterButton->setToolTip(tr("Filter Test Results")); m_filterButton->setProperty("noArrow", true); - m_filterButton->setAutoRaise(true); m_filterButton->setPopupMode(QToolButton::InstantPopup); m_filterMenu = new QMenu(m_filterButton); initializeFilterMenu(); @@ -228,9 +229,9 @@ void TestResultsPane::addTestResult(const TestResultPtr &result) m_atEnd = scrollBar ? scrollBar->value() == scrollBar->maximum() : true; m_model->addTestResult(result, m_expandCollapse->isChecked()); - setIconBadgeNumber(m_model->resultTypeCount(Result::Fail) - + m_model->resultTypeCount(Result::MessageFatal) - + m_model->resultTypeCount(Result::UnexpectedPass)); + setIconBadgeNumber(m_model->resultTypeCount(ResultType::Fail) + + m_model->resultTypeCount(ResultType::MessageFatal) + + m_model->resultTypeCount(ResultType::UnexpectedPass)); flash(); navigateStateChanged(); } @@ -423,24 +424,24 @@ void TestResultsPane::initializeFilterMenu() const bool omitIntern = AutotestPlugin::settings()->omitInternalMssg; // FilterModel has all messages enabled by default if (omitIntern) - m_filterModel->toggleTestResultType(Result::MessageInternal); - - QMap<Result::Type, QString> textAndType; - textAndType.insert(Result::Pass, tr("Pass")); - textAndType.insert(Result::Fail, tr("Fail")); - textAndType.insert(Result::ExpectedFail, tr("Expected Fail")); - textAndType.insert(Result::UnexpectedPass, tr("Unexpected Pass")); - textAndType.insert(Result::Skip, tr("Skip")); - textAndType.insert(Result::Benchmark, tr("Benchmarks")); - textAndType.insert(Result::MessageDebug, tr("Debug Messages")); - textAndType.insert(Result::MessageWarn, tr("Warning Messages")); - textAndType.insert(Result::MessageInternal, tr("Internal Messages")); - for (Result::Type result : textAndType.keys()) { + m_filterModel->toggleTestResultType(ResultType::MessageInternal); + + QMap<ResultType, QString> textAndType; + textAndType.insert(ResultType::Pass, tr("Pass")); + textAndType.insert(ResultType::Fail, tr("Fail")); + textAndType.insert(ResultType::ExpectedFail, tr("Expected Fail")); + textAndType.insert(ResultType::UnexpectedPass, tr("Unexpected Pass")); + textAndType.insert(ResultType::Skip, tr("Skip")); + textAndType.insert(ResultType::Benchmark, tr("Benchmarks")); + textAndType.insert(ResultType::MessageDebug, tr("Debug Messages")); + textAndType.insert(ResultType::MessageWarn, tr("Warning Messages")); + textAndType.insert(ResultType::MessageInternal, tr("Internal Messages")); + for (ResultType result : textAndType.keys()) { QAction *action = new QAction(m_filterMenu); action->setText(textAndType.value(result)); action->setCheckable(true); - action->setChecked(result != Result::MessageInternal || !omitIntern); - action->setData(result); + action->setChecked(result != ResultType::MessageInternal || !omitIntern); + action->setData(int(result)); m_filterMenu->addAction(action); } m_filterMenu->addSeparator(); @@ -457,26 +458,26 @@ void TestResultsPane::updateSummaryLabel() QString labelText = QString("<p>"); labelText.append(tr("Test summary")); labelText.append(": "); - int count = m_model->resultTypeCount(Result::Pass); + int count = m_model->resultTypeCount(ResultType::Pass); labelText += QString::number(count) + ' ' + tr("passes"); - count = m_model->resultTypeCount(Result::Fail); + count = m_model->resultTypeCount(ResultType::Fail); labelText += ", " + QString::number(count) + ' ' + tr("fails"); - count = m_model->resultTypeCount(Result::UnexpectedPass); + count = m_model->resultTypeCount(ResultType::UnexpectedPass); if (count) labelText += ", " + QString::number(count) + ' ' + tr("unexpected passes"); - count = m_model->resultTypeCount(Result::ExpectedFail); + count = m_model->resultTypeCount(ResultType::ExpectedFail); if (count) labelText += ", " + QString::number(count) + ' ' + tr("expected fails"); - count = m_model->resultTypeCount(Result::MessageFatal); + count = m_model->resultTypeCount(ResultType::MessageFatal); if (count) labelText += ", " + QString::number(count) + ' ' + tr("fatals"); - count = m_model->resultTypeCount(Result::BlacklistedFail) - + m_model->resultTypeCount(Result::BlacklistedXFail) - + m_model->resultTypeCount(Result::BlacklistedPass) - + m_model->resultTypeCount(Result::BlacklistedXPass); + 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("blacklisted"); - count = m_model->resultTypeCount(Result::Skip); + count = m_model->resultTypeCount(ResultType::Skip); if (count) labelText += ", " + QString::number(count) + ' ' + tr("skipped"); count = m_model->disabledTests(); @@ -509,6 +510,13 @@ void TestResultsPane::onTestRunStarted() 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; @@ -520,8 +528,10 @@ void TestResultsPane::onTestRunFinished() m_model->removeCurrentTestMessage(); disconnect(m_treeView->verticalScrollBar(), &QScrollBar::rangeChanged, this, &TestResultsPane::onScrollBarRangeChanged); - if (!m_treeView->isVisible()) + if (AutotestPlugin::settings()->popupOnFinish + && (!AutotestPlugin::settings()->popupOnFail || hasFailedTests(m_model))) { popup(Core::IOutputPane::NoModeSwitch); + } createMarks(); } @@ -637,7 +647,8 @@ QString TestResultsPane::getWholeOutput(const QModelIndex &parent) QModelIndex current = m_model->index(row, 0, parent); const TestResult *result = m_model->testResult(current); QTC_ASSERT(result, continue); - output.append(TestResult::resultToString(result->result())).append('\t'); + if (auto item = m_model->itemForIndex(current)) + output.append(item->resultString()).append('\t'); output.append(result->outputString(true)).append('\n'); output.append(getWholeOutput(current)); } @@ -647,8 +658,8 @@ QString TestResultsPane::getWholeOutput(const QModelIndex &parent) void TestResultsPane::createMarks(const QModelIndex &parent) { const TestResult *parentResult = m_model->testResult(parent); - Result::Type parentType = parentResult ? parentResult->result() : Result::Invalid; - const QVector<Result::Type> interested{Result::Fail, Result::UnexpectedPass}; + ResultType parentType = parentResult ? parentResult->result() : ResultType::Invalid; + const QVector<ResultType> 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); @@ -657,10 +668,10 @@ void TestResultsPane::createMarks(const QModelIndex &parent) if (m_model->hasChildren(index)) createMarks(index); - bool isLocationItem = result->result() == Result::MessageLocation; + bool isLocationItem = result->result() == ResultType::MessageLocation; if (interested.contains(result->result()) || (isLocationItem && interested.contains(parentType))) { - const Utils::FileName fileName = Utils::FileName::fromString(result->fileName()); + const Utils::FilePath fileName = Utils::FilePath::fromString(result->fileName()); TestEditorMark *mark = new TestEditorMark(index, fileName, result->line()); mark->setIcon(index.data(Qt::DecorationRole).value<QIcon>()); mark->setColor(Utils::Theme::OutputPanes_TestFailTextColor); diff --git a/src/plugins/autotest/testrunner.cpp b/src/plugins/autotest/testrunner.cpp index 6792b96cb3..cf432870e3 100644 --- a/src/plugins/autotest/testrunner.cpp +++ b/src/plugins/autotest/testrunner.cpp @@ -89,8 +89,7 @@ TestRunner::TestRunner(QObject *parent) : connect(&m_futureWatcher, &QFutureWatcher<TestResultPtr>::canceled, this, [this]() { cancelCurrent(UserCanceled); - emit testResultReady(TestResultPtr(new FaultyTestResult( - Result::MessageFatal, tr("Test run canceled by user.")))); + reportResult(ResultType::MessageFatal, tr("Test run canceled by user.")); }); } @@ -171,8 +170,8 @@ void TestRunner::scheduleNext() QString commandFilePath = m_currentConfig->executableFilePath(); if (commandFilePath.isEmpty()) { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal, - tr("Executable path is empty. (%1)").arg(m_currentConfig->displayName())))); + reportResult(ResultType::MessageFatal, + tr("Executable path is empty. (%1)").arg(m_currentConfig->displayName())); delete m_currentConfig; m_currentConfig = nullptr; if (m_selectedTests.isEmpty()) @@ -200,8 +199,7 @@ void TestRunner::scheduleNext() m_currentProcess->setArguments(m_currentConfig->argumentsForTestRunner(&omitted)); if (!omitted.isEmpty()) { const QString &details = constructOmittedDetailsString(omitted); - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageWarn, - details.arg(m_currentConfig->displayName())))); + reportResult(ResultType::MessageWarn, details.arg(m_currentConfig->displayName())); } m_currentProcess->setWorkingDirectory(m_currentConfig->workingDirectory()); const Utils::Environment &original = m_currentConfig->environment(); @@ -213,21 +211,20 @@ void TestRunner::scheduleNext() if (!removedVariables.isEmpty()) { const QString &details = constructOmittedVariablesDetailsString(removedVariables) .arg(m_currentConfig->displayName()); - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageWarn, details))); + reportResult(ResultType::MessageWarn, details); } m_currentProcess->setProcessEnvironment(environment.toProcessEnvironment()); - connect(m_currentProcess, - static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), + connect(m_currentProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &TestRunner::onProcessFinished); const int timeout = AutotestPlugin::settings()->timeout; QTimer::singleShot(timeout, m_currentProcess, [this]() { cancelCurrent(Timeout); }); m_currentProcess->start(); if (!m_currentProcess->waitForStarted()) { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal, + reportResult(ResultType::MessageFatal, tr("Failed to start test for project \"%1\".").arg(m_currentConfig->displayName()) - + processInformation(m_currentProcess) + rcInfo(m_currentConfig)))); + + processInformation(m_currentProcess) + rcInfo(m_currentConfig)); } } @@ -238,14 +235,10 @@ void TestRunner::cancelCurrent(TestRunner::CancelReason reason) if (m_fakeFutureInterface) m_fakeFutureInterface->reportCanceled(); - auto reportResult = [this](Result::Type type, const QString &detail){ - emit testResultReady(TestResultPtr(new FaultyTestResult(type, detail))); - }; - if (reason == KitChanged) - reportResult(Result::MessageWarn, tr("Current kit has changed. Canceling test run.")); + reportResult(ResultType::MessageWarn, tr("Current kit has changed. Canceling test run.")); else if (reason == Timeout) - reportResult(Result::MessageFatal, tr("Test case canceled due to timeout.\nMaybe raise the timeout?")); + reportResult(ResultType::MessageFatal, tr("Test case canceled due to timeout.\nMaybe raise the timeout?")); // if user or timeout cancels the current run ensure to kill the running process if (m_currentProcess && m_currentProcess->state() != QProcess::NotRunning) { @@ -263,17 +256,23 @@ void TestRunner::onProcessFinished() if (!m_fakeFutureInterface->isCanceled()) { if (m_currentProcess->exitStatus() == QProcess::CrashExit) { m_currentOutputReader->reportCrash(); - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal, + reportResult(ResultType::MessageFatal, tr("Test for project \"%1\" crashed.").arg(m_currentConfig->displayName()) - + processInformation(m_currentProcess) + rcInfo(m_currentConfig)))); + + processInformation(m_currentProcess) + rcInfo(m_currentConfig)); } else if (!m_currentOutputReader->hadValidOutput()) { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal, + reportResult(ResultType::MessageFatal, tr("Test for project \"%1\" did not produce any expected output.") .arg(m_currentConfig->displayName()) + processInformation(m_currentProcess) - + rcInfo(m_currentConfig)))); + + rcInfo(m_currentConfig)); } } } + const int disabled = m_currentOutputReader->disabledTests(); + if (disabled > 0) + emit hadDisabledTests(disabled); + if (m_currentOutputReader->hasSummary()) + emit reportSummary(m_currentOutputReader->id(), m_currentOutputReader->summary()); + resetInternalPointers(); if (!m_fakeFutureInterface) { @@ -315,18 +314,17 @@ void TestRunner::prepareToRunTests(TestRunMode mode) TestResultsPane::instance()->clearContents(); if (m_selectedTests.empty()) { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageWarn, - tr("No tests selected. Canceling test run.")))); + reportResult(ResultType::MessageWarn, tr("No tests selected. Canceling test run.")); onFinished(); return; } ProjectExplorer::Project *project = m_selectedTests.at(0)->project(); if (!project) { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageWarn, + reportResult(ResultType::MessageWarn, tr("Project is null. Canceling test run.\n" - "Only desktop kits are supported. Make sure the " - "currently active kit is a desktop kit.")))); + "Only desktop kits are supported. Make sure the " + "currently active kit is a desktop kit.")); onFinished(); return; } @@ -340,8 +338,8 @@ void TestRunner::prepareToRunTests(TestRunMode mode) } else if (project->hasActiveBuildSettings()) { buildProject(project); } else { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal, - tr("Project is not configured. Canceling test run.")))); + reportResult(ResultType::MessageFatal, + tr("Project is not configured. Canceling test run.")); onFinished(); } } @@ -414,13 +412,12 @@ int TestRunner::precheckTestConfigurations() "This might cause trouble during execution.\n" "(deduced from \"%2\")"); message = message.arg(config->displayName()).arg(config->runConfigDisplayName()); - emit testResultReady( - TestResultPtr(new FaultyTestResult(Result::MessageWarn, message))); + reportResult(ResultType::MessageWarn, message); } } else { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageWarn, - tr("Project is null for \"%1\". Removing from test run.\n" - "Check the test environment.").arg(config->displayName())))); + reportResult(ResultType::MessageWarn, + tr("Project is null for \"%1\". Removing from test run.\n" + "Check the test environment.").arg(config->displayName())); } } return testCaseCount; @@ -450,7 +447,7 @@ void TestRunner::runTests() QString mssg = projectChanged ? tr("Startup project has changed. Canceling test run.") : tr("No test cases left for execution. Canceling test run."); - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageWarn, mssg))); + reportResult(ResultType::MessageWarn, mssg); onFinished(); return; } @@ -465,6 +462,8 @@ void TestRunner::runTests() m_futureWatcher.setFuture(future); Core::ProgressManager::addTask(future, tr("Running Tests"), Autotest::Constants::TASK_INDEX); + if (AutotestPlugin::settings()->popupOnStart) + AutotestPlugin::popupResultsPane(); scheduleNext(); } @@ -514,8 +513,8 @@ void TestRunner::debugTests() TestConfiguration *config = m_selectedTests.first(); config->completeTestInformation(TestRunMode::Debug); if (!config->project()) { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageWarn, - TestRunner::tr("Startup project has changed. Canceling test run.")))); + reportResult(ResultType::MessageWarn, + tr("Startup project has changed. Canceling test run.")); onFinished(); return; } @@ -525,28 +524,26 @@ void TestRunner::debugTests() } if (!config->runConfiguration()) { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal, - TestRunner::tr("Failed to get run configuration.")))); + reportResult(ResultType::MessageFatal, tr("Failed to get run configuration.")); onFinished(); return; } const QString &commandFilePath = config->executableFilePath(); if (commandFilePath.isEmpty()) { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal, - TestRunner::tr("Could not find command \"%1\". (%2)") - .arg(config->executableFilePath()) - .arg(config->displayName())))); + reportResult(ResultType::MessageFatal, tr("Could not find command \"%1\". (%2)") + .arg(config->executableFilePath()) + .arg(config->displayName())); onFinished(); return; } QString errorMessage; - auto runControl = new ProjectExplorer::RunControl(config->runConfiguration(), - ProjectExplorer::Constants::DEBUG_RUN_MODE); + auto runControl = new ProjectExplorer::RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE); + runControl->setRunConfiguration(config->runConfiguration()); if (!runControl) { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal, - TestRunner::tr("Failed to create run configuration.\n%1").arg(errorMessage)))); + reportResult(ResultType::MessageFatal, + tr("Failed to create run configuration.\n%1").arg(errorMessage)); onFinished(); return; } @@ -559,8 +556,7 @@ void TestRunner::debugTests() inferior.commandLineArguments = Utils::QtcProcess::joinArgs(args); if (!omitted.isEmpty()) { const QString &details = constructOmittedDetailsString(omitted); - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageWarn, - details.arg(config->displayName())))); + reportResult(ResultType::MessageWarn, details.arg(config->displayName())); } Utils::Environment original(inferior.environment); inferior.environment = config->filteredEnvironment(original); @@ -571,7 +567,7 @@ void TestRunner::debugTests() if (!removedVariables.isEmpty()) { const QString &details = constructOmittedVariablesDetailsString(removedVariables) .arg(config->displayName()); - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageWarn, details))); + reportResult(ResultType::MessageWarn, details); } auto debugger = new Debugger::DebuggerRunTool(runControl); debugger->setInferior(inferior); @@ -579,9 +575,9 @@ void TestRunner::debugTests() bool useOutputProcessor = true; if (ProjectExplorer::Target *targ = config->project()->activeTarget()) { - if (Debugger::DebuggerKitInformation::engineType(targ->kit()) == Debugger::CdbEngineType) { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageWarn, - TestRunner::tr("Unable to display test results when using CDB.")))); + if (Debugger::DebuggerKitAspect::engineType(targ->kit()) == Debugger::CdbEngineType) { + reportResult(ResultType::MessageWarn, + tr("Unable to display test results when using CDB.")); useOutputProcessor = false; } } @@ -596,9 +592,8 @@ void TestRunner::debugTests() outputreader->setId(inferior.executable); connect(outputreader, &TestOutputReader::newOutputAvailable, TestResultsPane::instance(), &TestResultsPane::addOutput); - connect(runControl, &ProjectExplorer::RunControl::appendMessageRequested, - this, [outputreader] - (ProjectExplorer::RunControl *, const QString &msg, Utils::OutputFormat format) { + connect(runControl, &ProjectExplorer::RunControl::appendMessage, + this, [outputreader](const QString &msg, Utils::OutputFormat format) { processOutput(outputreader, msg, format); }); @@ -611,6 +606,8 @@ void TestRunner::debugTests() connect(runControl, &ProjectExplorer::RunControl::stopped, this, &TestRunner::onFinished); ProjectExplorer::ProjectExplorerPlugin::startRunControl(runControl); + if (useOutputProcessor && AutotestPlugin::settings()->popupOnStart) + AutotestPlugin::popupResultsPane(); } void TestRunner::runOrDebugTests() @@ -654,8 +651,7 @@ void TestRunner::buildFinished(bool success) else if (m_executingTests) onFinished(); } else { - emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal, - tr("Build failed. Canceling test run.")))); + reportResult(ResultType::MessageFatal, tr("Build failed. Canceling test run.")); onFinished(); } } @@ -673,6 +669,14 @@ void TestRunner::onFinished() emit testRunFinished(); } +void TestRunner::reportResult(ResultType type, const QString &description) +{ + TestResultPtr result(new TestResult); + result->setResult(type); + result->setDescription(description); + emit testResultReady(result); +} + /*************************************************************************************************/ static QFrame *createLine(QWidget *parent) diff --git a/src/plugins/autotest/testrunner.h b/src/plugins/autotest/testrunner.h index b56d797401..5628a6d29e 100644 --- a/src/plugins/autotest/testrunner.h +++ b/src/plugins/autotest/testrunner.h @@ -70,6 +70,8 @@ signals: void testRunFinished(); void requestStopTestRun(); void testResultReady(const TestResultPtr &result); + void hadDisabledTests(int disabled); + void reportSummary(const QString &id, const QHash<ResultType, int> &summary); private: void buildProject(ProjectExplorer::Project *project); @@ -85,6 +87,7 @@ private: void runTests(); void debugTests(); void runOrDebugTests(); + void reportResult(ResultType type, const QString &description); explicit TestRunner(QObject *parent = nullptr); QFutureWatcher<TestResultPtr> m_futureWatcher; diff --git a/src/plugins/autotest/testsettings.cpp b/src/plugins/autotest/testsettings.cpp index c825ea5573..ddb4f49e81 100644 --- a/src/plugins/autotest/testsettings.cpp +++ b/src/plugins/autotest/testsettings.cpp @@ -39,10 +39,11 @@ static const char omitInternalKey[] = "OmitInternal"; static const char omitRunConfigWarnKey[] = "OmitRCWarnings"; static const char limitResultOutputKey[] = "LimitResultOutput"; static const char autoScrollKey[] = "AutoScrollResults"; -static const char filterScanKey[] = "FilterScan"; -static const char filtersKey[] = "WhiteListFilters"; static const char processArgsKey[] = "ProcessArgs"; static const char displayApplicationKey[] = "DisplayApp"; +static const char popupOnStartKey[] = "PopupOnStart"; +static const char popupOnFinishKey[] = "PopupOnFinish"; +static const char popupOnFailKey[] = "PopupOnFail"; static const char groupSuffix[] = ".group"; constexpr int defaultTimeout = 60000; @@ -62,8 +63,9 @@ void TestSettings::toSettings(QSettings *s) const s->setValue(autoScrollKey, autoScroll); s->setValue(processArgsKey, processArgs); s->setValue(displayApplicationKey, displayApplication); - s->setValue(filterScanKey, filterScan); - s->setValue(filtersKey, whiteListFilters); + s->setValue(popupOnStartKey, popupOnStart); + s->setValue(popupOnFinishKey, popupOnFinish); + s->setValue(popupOnFailKey, popupOnFail); // store frameworks and their current active and grouping state for (const Core::Id &id : frameworks.keys()) { s->setValue(QLatin1String(id.name()), frameworks.value(id)); @@ -82,8 +84,9 @@ void TestSettings::fromSettings(QSettings *s) autoScroll = s->value(autoScrollKey, true).toBool(); processArgs = s->value(processArgsKey, false).toBool(); displayApplication = s->value(displayApplicationKey, false).toBool(); - filterScan = s->value(filterScanKey, false).toBool(); - whiteListFilters = s->value(filtersKey, QStringList()).toStringList(); + popupOnStart = s->value(popupOnStartKey, true).toBool(); + popupOnFinish = s->value(popupOnFinishKey, true).toBool(); + popupOnFail = s->value(popupOnFailKey, false).toBool(); // try to get settings for registered frameworks TestFrameworkManager *frameworkManager = TestFrameworkManager::instance(); const QList<Core::Id> ®istered = frameworkManager->registeredFrameworkIds(); diff --git a/src/plugins/autotest/testsettings.h b/src/plugins/autotest/testsettings.h index 6eff083e59..92c4ef9933 100644 --- a/src/plugins/autotest/testsettings.h +++ b/src/plugins/autotest/testsettings.h @@ -47,12 +47,13 @@ struct TestSettings bool omitRunConfigWarn = false; bool limitResultOutput = true; bool autoScroll = true; - bool filterScan = false; bool processArgs = false; bool displayApplication = false; + bool popupOnStart = true; + bool popupOnFinish = true; + bool popupOnFail = false; QHash<Core::Id, bool> frameworks; QHash<Core::Id, bool> frameworksGrouping; - QStringList whiteListFilters; }; } // namespace Internal diff --git a/src/plugins/autotest/testsettingspage.cpp b/src/plugins/autotest/testsettingspage.cpp index e2bf15ae6c..169bab7320 100644 --- a/src/plugins/autotest/testsettingspage.cpp +++ b/src/plugins/autotest/testsettingspage.cpp @@ -32,90 +32,12 @@ #include "autotestplugin.h" #include <coreplugin/icore.h> -#include <utils/fancylineedit.h> #include <utils/qtcassert.h> #include <utils/utilsicons.h> -#include <QDialog> -#include <QDialogButtonBox> -#include <QRegExp> - namespace Autotest { namespace Internal { -class TestFilterDialog : public QDialog -{ -public: - explicit TestFilterDialog(QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); - QString filterPath() const; - void setDetailsText(const QString &details) { m_details->setText(details); } - void setDefaultFilterPath(const QString &defaultPath); -private: - static bool validate(Utils::FancyLineEdit *edit, QString * /*errormessage*/); - QLabel *m_details; - Utils::FancyLineEdit *m_lineEdit; - QString m_defaultPath; -}; - -TestFilterDialog::TestFilterDialog(QWidget *parent, Qt::WindowFlags f) - : QDialog(parent, f), - m_details(new QLabel), - m_lineEdit(new Utils::FancyLineEdit) -{ - setModal(true); - auto layout = new QVBoxLayout(this); - layout->setSizeConstraint(QLayout::SetFixedSize); - layout->addWidget(m_details); - m_lineEdit->setValidationFunction(&validate); - layout->addWidget(m_lineEdit); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - auto okButton = buttonBox->button(QDialogButtonBox::Ok); - auto cancelButton = buttonBox->button(QDialogButtonBox::Cancel); - okButton->setEnabled(false); - layout->addWidget(buttonBox); - setLayout(layout); - connect(m_lineEdit, &Utils::FancyLineEdit::validChanged, okButton, &QPushButton::setEnabled); - connect(okButton, &QPushButton::clicked, this, &QDialog::accept); - connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); -} - -QString TestFilterDialog::filterPath() const -{ - static const QRegExp repetition("//+"); - QString path = m_lineEdit->isValid() ? m_lineEdit->text() : m_defaultPath; - path.replace('\\', '/'); // turn windows separators into forward slashes - path.replace(repetition, "/"); // avoid duplicated separators - if (!path.startsWith('/')) - path.prepend('/'); - if (!path.endsWith('/')) - path.append('/'); - if (path.length() <= 2) // after surrounding with '/' this should be > 2 if valid - return QString(); // empty string marks invalid filter - return path; -} - -void TestFilterDialog::setDefaultFilterPath(const QString &defaultPath) -{ - m_lineEdit->setText(defaultPath); - if (m_lineEdit->isValid()) - m_defaultPath = defaultPath; - else - m_lineEdit->setText(m_defaultPath); -} - -bool TestFilterDialog::validate(Utils::FancyLineEdit *edit, QString *) -{ - if (!edit) - return false; - const QString &value = edit->text(); - if (value.isEmpty()) - return false; - // should we distinguish between Windows and UNIX? - static const QRegExp valid("(\\|/)?([^?*:;\"\'|<>\t\b]+(\\|/)?)+"); - return valid.exactMatch(value); -} -/**************************************************************************************************/ - TestSettingsWidget::TestSettingsWidget(QWidget *parent) : QWidget(parent) { @@ -129,19 +51,10 @@ TestSettingsWidget::TestSettingsWidget(QWidget *parent) "having at least one active test framework.")); connect(m_ui.frameworkTreeWidget, &QTreeWidget::itemChanged, this, &TestSettingsWidget::onFrameworkItemChanged); - connect(m_ui.addFilter, &QPushButton::clicked, this, &TestSettingsWidget::onAddFilterClicked); - connect(m_ui.editFilter, &QPushButton::clicked, this, &TestSettingsWidget::onEditFilterClicked); - connect(m_ui.filterTreeWidget, &QTreeWidget::activated, - this, &TestSettingsWidget::onEditFilterClicked); - connect(m_ui.removeFilter, &QPushButton::clicked, - this, &TestSettingsWidget::onRemoveFilterClicked); - connect(m_ui.filterTreeWidget, &QTreeWidget::itemSelectionChanged, [this] () { - const bool enable = m_ui.filterTreeWidget->selectionModel()->hasSelection(); - m_ui.editFilter->setEnabled(enable); - m_ui.removeFilter->setEnabled(enable); - }); connect(m_ui.resetChoicesButton, &QPushButton::clicked, this, [] { AutotestPlugin::clearChoiceCache(); }); + connect(m_ui.openResultsOnFinishCB, &QCheckBox::toggled, + m_ui.openResultsOnFailCB, &QCheckBox::setEnabled); } void TestSettingsWidget::setSettings(const TestSettings &settings) @@ -153,9 +66,10 @@ void TestSettingsWidget::setSettings(const TestSettings &settings) m_ui.autoScrollCB->setChecked(settings.autoScroll); m_ui.processArgsCB->setChecked(settings.processArgs); m_ui.displayAppCB->setChecked(settings.displayApplication); - m_ui.filterGroupBox->setChecked(settings.filterScan); + m_ui.openResultsOnStartCB->setChecked(settings.popupOnStart); + m_ui.openResultsOnFinishCB->setChecked(settings.popupOnFinish); + m_ui.openResultsOnFailCB->setChecked(settings.popupOnFail); populateFrameworksListWidget(settings.frameworks); - populateFiltersWidget(settings.whiteListFilters); } TestSettings TestSettingsWidget::settings() const @@ -168,9 +82,10 @@ TestSettings TestSettingsWidget::settings() const result.autoScroll = m_ui.autoScrollCB->isChecked(); result.processArgs = m_ui.processArgsCB->isChecked(); result.displayApplication = m_ui.displayAppCB->isChecked(); - result.filterScan = m_ui.filterGroupBox->isChecked(); + result.popupOnStart = m_ui.openResultsOnStartCB->isChecked(); + result.popupOnFinish = m_ui.openResultsOnFinishCB->isChecked(); + result.popupOnFail = m_ui.openResultsOnFailCB->isChecked(); frameworkSettings(result); - result.whiteListFilters = filters(); return result; } @@ -196,12 +111,6 @@ void TestSettingsWidget::populateFrameworksListWidget(const QHash<Core::Id, bool } } -void TestSettingsWidget::populateFiltersWidget(const QStringList &filters) -{ - for (const QString &filter : filters) - new QTreeWidgetItem(m_ui.filterTreeWidget, {filter} ); -} - void TestSettingsWidget::frameworkSettings(TestSettings &settings) const { const QAbstractItemModel *model = m_ui.frameworkTreeWidget->model(); @@ -216,16 +125,6 @@ void TestSettingsWidget::frameworkSettings(TestSettings &settings) const } } -QStringList TestSettingsWidget::filters() const -{ - QStringList result; - if (QAbstractItemModel *model = m_ui.filterTreeWidget->model()) { - for (int row = 0, count = model->rowCount(); row < count; ++row) - result.append(model->index(row, 0).data().toString()); - } - return result; -} - void TestSettingsWidget::onFrameworkItemChanged() { if (QAbstractItemModel *model = m_ui.frameworkTreeWidget->model()) { @@ -241,45 +140,6 @@ void TestSettingsWidget::onFrameworkItemChanged() m_ui.frameworksWarnIcon->setVisible(true); } -void TestSettingsWidget::onAddFilterClicked() -{ - TestFilterDialog dialog; - dialog.setWindowTitle(tr("Add Filter")); - dialog.setDetailsText("<p>" + tr("Specify a filter expression to be added to the list of filters." - "<br/>Wildcards are not supported.") + "</p>"); - if (dialog.exec() == QDialog::Accepted) { - const QString &filter = dialog.filterPath(); - if (!filter.isEmpty()) - new QTreeWidgetItem(m_ui.filterTreeWidget, {filter} ); - } -} - -void TestSettingsWidget::onEditFilterClicked() -{ - const QList<QTreeWidgetItem *> &selected = m_ui.filterTreeWidget->selectedItems(); - QTC_ASSERT(selected.size() == 1, return); - const QString &oldFilter = selected.first()->data(0, Qt::DisplayRole).toString(); - - TestFilterDialog dialog; - dialog.setWindowTitle(tr("Edit Filter")); - dialog.setDetailsText("<p>" + tr("Specify a filter expression that will replace \"%1\"." - "<br/>Wildcards are not supported.").arg(oldFilter) + "</p>"); - dialog.setDefaultFilterPath(oldFilter); - if (dialog.exec() == QDialog::Accepted) { - const QString &edited = dialog.filterPath(); - if (!edited.isEmpty() && edited != oldFilter) - selected.first()->setData(0, Qt::DisplayRole, edited); - } -} - -void TestSettingsWidget::onRemoveFilterClicked() -{ - const QList<QTreeWidgetItem *> &selected = m_ui.filterTreeWidget->selectedItems(); - QTC_ASSERT(selected.size() == 1, return); - m_ui.filterTreeWidget->removeItemWidget(selected.first(), 0); - delete selected.first(); -} - TestSettingsPage::TestSettingsPage(const QSharedPointer<TestSettings> &settings) : m_settings(settings) { @@ -306,8 +166,6 @@ void TestSettingsPage::apply() return; const TestSettings newSettings = m_widget->settings(); bool frameworkSyncNecessary = newSettings.frameworks != m_settings->frameworks; - bool forceReparse = newSettings.filterScan != m_settings->filterScan || - newSettings.whiteListFilters.toSet() != m_settings->whiteListFilters.toSet(); const QList<Core::Id> changedIds = Utils::filtered(newSettings.frameworksGrouping.keys(), [newSettings, this] (const Core::Id &id) { return newSettings.frameworksGrouping[id] != m_settings->frameworksGrouping[id]; @@ -318,8 +176,6 @@ void TestSettingsPage::apply() frameworkManager->activateFrameworksFromSettings(m_settings); if (frameworkSyncNecessary) TestTreeModel::instance()->syncTestFrameworks(); - else if (forceReparse) - TestTreeModel::instance()->parser()->emitUpdateTestTree(); else if (!changedIds.isEmpty()) TestTreeModel::instance()->rebuild(changedIds); } diff --git a/src/plugins/autotest/testsettingspage.h b/src/plugins/autotest/testsettingspage.h index 2b1216c9eb..a100500476 100644 --- a/src/plugins/autotest/testsettingspage.h +++ b/src/plugins/autotest/testsettingspage.h @@ -47,13 +47,8 @@ public: private: void populateFrameworksListWidget(const QHash<Core::Id, bool> &frameworks); - void populateFiltersWidget(const QStringList &filters); void frameworkSettings(TestSettings &settings) const; - QStringList filters() const; void onFrameworkItemChanged(); - void onAddFilterClicked(); - void onEditFilterClicked(); - void onRemoveFilterClicked(); Ui::TestSettingsPage m_ui; }; diff --git a/src/plugins/autotest/testsettingspage.ui b/src/plugins/autotest/testsettingspage.ui index dbdf49842a..cb65055bc3 100644 --- a/src/plugins/autotest/testsettingspage.ui +++ b/src/plugins/autotest/testsettingspage.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>585</width> - <height>458</height> + <width>586</width> + <height>429</height> </rect> </property> <property name="windowTitle"> @@ -61,6 +61,59 @@ </widget> </item> <item> + <widget class="QCheckBox" name="openResultsOnStartCB"> + <property name="toolTip"> + <string>Opens the test results pane automatically when tests are started.</string> + </property> + <property name="text"> + <string>Open results pane when tests start</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="openResultsOnFinishCB"> + <property name="toolTip"> + <string>Opens the test result pane automatically when tests are finished.</string> + </property> + <property name="text"> + <string>Open results pane when tests finish</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>10</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="openResultsOnFailCB"> + <property name="toolTip"> + <string>Opens the test result pane only if the test run contains failed, fatal or unexpectedly passed tests.</string> + </property> + <property name="text"> + <string>Only for unsuccessful test runs</string> + </property> + </widget> + </item> + </layout> + </item> + <item> <widget class="QCheckBox" name="autoScrollCB"> <property name="toolTip"> <string>Automatically scrolls down when new items are added and scrollbar is at bottom.</string> @@ -284,102 +337,6 @@ Warning: this is an experimental feature and might lead to failing to execute th </layout> </item> <item> - <widget class="QGroupBox" name="filterGroupBox"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="title"> - <string>Global Filters</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="checked"> - <bool>false</bool> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QTreeWidget" name="filterTreeWidget"> - <property name="toolTip"> - <string>Filters used on directories when scanning for tests.<br/>If filtering is enabled, only directories that match any of the filters will be scanned.</string> - </property> - <property name="uniformRowHeights"> - <bool>true</bool> - </property> - <property name="headerHidden"> - <bool>true</bool> - </property> - <column> - <property name="text"> - <string notr="true">1</string> - </property> - </column> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QPushButton" name="addFilter"> - <property name="text"> - <string>Add...</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="editFilter"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Edit...</string> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_5"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="removeFilter"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Remove</string> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_4"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> <spacer name="verticalSpacer_3"> <property name="orientation"> <enum>Qt::Vertical</enum> diff --git a/src/plugins/autotest/testtreeitem.cpp b/src/plugins/autotest/testtreeitem.cpp index e1c1debe9c..bc3c4f16be 100644 --- a/src/plugins/autotest/testtreeitem.cpp +++ b/src/plugins/autotest/testtreeitem.cpp @@ -47,8 +47,9 @@ TestTreeItem::TestTreeItem(const QString &name, const QString &filePath, Type ty switch (m_type) { case Root: case GroupNode: + case TestSuite: case TestCase: - case TestFunctionOrSet: + case TestFunction: m_checked = Qt::Checked; break; default: @@ -62,6 +63,7 @@ static QIcon testTreeIcon(TestTreeItem::Type type) static QIcon icons[] = { QIcon(), Utils::Icons::OPENFILE.icon(), + QIcon(":/autotest/images/suite.png"), Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Class), Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::SlotPrivate), QIcon(":/autotest/images/data.png") @@ -120,16 +122,17 @@ Qt::ItemFlags TestTreeItem::flags(int /*column*/) const case Root: case GroupNode: return Qt::ItemIsEnabled | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable; + case TestSuite: case TestCase: return defaultFlags | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable; - case TestFunctionOrSet: + case TestFunction: return defaultFlags | Qt::ItemIsUserCheckable; default: return defaultFlags; } } -bool TestTreeItem::modifyTestCaseContent(const TestParseResult *result) +bool TestTreeItem::modifyTestCaseOrSuiteContent(const TestParseResult *result) { bool hasBeenModified = modifyName(result->name); hasBeenModified |= modifyLineAndColumn(result); @@ -170,8 +173,9 @@ Qt::CheckState TestTreeItem::checked() const switch (m_type) { case Root: case GroupNode: + case TestSuite: case TestCase: - case TestFunctionOrSet: + case TestFunction: case TestDataTag: return m_checked; default: @@ -255,7 +259,7 @@ QList<TestConfiguration *> TestTreeItem::getSelectedTestConfigurations() const return QList<TestConfiguration *>(); } -QList<TestConfiguration *> TestTreeItem::getTestConfigurationsForFile(const Utils::FileName &) const +QList<TestConfiguration *> TestTreeItem::getTestConfigurationsForFile(const Utils::FilePath &) const { return QList<TestConfiguration *>(); } @@ -364,9 +368,9 @@ QSet<QString> TestTreeItem::dependingInternalTargets(CppTools::CppModelManager * bool wasHeader; const QString correspondingFile = CppTools::correspondingHeaderOrSource(file, &wasHeader, CppTools::CacheUsage::ReadOnly); - const Utils::FileNameList dependingFiles = snapshot.filesDependingOn( + const Utils::FilePathList dependingFiles = snapshot.filesDependingOn( wasHeader ? file : correspondingFile); - for (const Utils::FileName &fn : dependingFiles) { + for (const Utils::FilePath &fn : dependingFiles) { for (const CppTools::ProjectPart::Ptr &part : cppMM->projectPart(fn)) result.insert(part->buildSystemTarget); } diff --git a/src/plugins/autotest/testtreeitem.h b/src/plugins/autotest/testtreeitem.h index 74e6c98d8f..e6d47bb820 100644 --- a/src/plugins/autotest/testtreeitem.h +++ b/src/plugins/autotest/testtreeitem.h @@ -42,7 +42,7 @@ namespace { } namespace CppTools { class CppModelManager; } -namespace Utils { class FileName; } +namespace Utils { class FilePath; } namespace Autotest { namespace Internal { @@ -59,8 +59,9 @@ public: { Root, GroupNode, + TestSuite, TestCase, - TestFunctionOrSet, + TestFunction, TestDataTag, TestDataFunction, TestSpecialFunction @@ -78,7 +79,7 @@ public: virtual QVariant data(int column, int role) const override; virtual bool setData(int column, const QVariant &data, int role) override; virtual Qt::ItemFlags flags(int column) const override; - bool modifyTestCaseContent(const TestParseResult *result); + bool modifyTestCaseOrSuiteContent(const TestParseResult *result); bool modifyTestFunctionContent(const TestParseResult *result); bool modifyDataTagContent(const TestParseResult *result); bool modifyLineAndColumn(const TestParseResult *result); @@ -115,7 +116,7 @@ public: TestConfiguration *asConfiguration(TestRunMode mode) const; virtual QList<TestConfiguration *> getAllTestConfigurations() const; virtual QList<TestConfiguration *> getSelectedTestConfigurations() const; - virtual QList<TestConfiguration *> getTestConfigurationsForFile(const Utils::FileName &fileName) const; + virtual QList<TestConfiguration *> getTestConfigurationsForFile(const Utils::FilePath &fileName) const; virtual bool lessThan(const TestTreeItem *other, SortMode mode) const; virtual TestTreeItem *find(const TestParseResult *result) = 0; virtual TestTreeItem *findChild(const TestTreeItem *other) = 0; @@ -126,6 +127,8 @@ public: // based on (internal) filters this will be used to filter out sub items (and remove them) // returns a copy of the item that contains the filtered out children or nullptr virtual TestTreeItem *applyFilters() { return nullptr; } + // decide whether an item should still be added to the treemodel + virtual bool shouldBeAddedAfterFiltering() const { return true; } virtual QSet<QString> internalTargets() const; protected: void copyBasicDataFrom(const TestTreeItem *other); diff --git a/src/plugins/autotest/testtreemodel.cpp b/src/plugins/autotest/testtreemodel.cpp index 7e4e577812..953e93dc9c 100644 --- a/src/plugins/autotest/testtreemodel.cpp +++ b/src/plugins/autotest/testtreemodel.cpp @@ -158,7 +158,7 @@ QList<TestConfiguration *> TestTreeModel::getSelectedTests() const return result; } -QList<TestConfiguration *> TestTreeModel::getTestsForFile(const Utils::FileName &fileName) const +QList<TestConfiguration *> TestTreeModel::getTestsForFile(const Utils::FilePath &fileName) const { QList<TestConfiguration *> result; for (Utils::TreeItem *frameworkRoot : *rootItem()) @@ -171,14 +171,14 @@ QList<TestTreeItem *> TestTreeModel::testItemsByName(TestTreeItem *root, const Q QList<TestTreeItem *> result; root->forFirstLevelChildren([&testName, &result, this](TestTreeItem *node) { - if (node->type() == TestTreeItem::TestCase) { + if (node->type() == TestTreeItem::TestSuite || node->type() == TestTreeItem::TestCase) { if (node->name() == testName) { result << node; - return; // prioritize Tests over TestCases + return; // prioritize test suites and cases over test functions } TestTreeItem *testCase = node->findFirstLevelChild([&testName](TestTreeItem *it) { QTC_ASSERT(it, return false); - return it->type() == TestTreeItem::TestFunctionOrSet && it->name() == testName; + return it->type() == TestTreeItem::TestFunction && it->name() == testName; }); // collect only actual tests, not special functions like init, cleanup etc. if (testCase) result << testCase; @@ -215,7 +215,7 @@ void TestTreeModel::syncTestFrameworks() void TestTreeModel::filterAndInsert(TestTreeItem *item, TestTreeItem *root, bool groupingEnabled) { TestTreeItem *filtered = item->applyFilters(); - if (item->type() != TestTreeItem::TestCase || item->childCount()) + if (item->shouldBeAddedAfterFiltering()) insertItemInParent(item, root, groupingEnabled); else // might be that all children have been filtered out delete item; @@ -481,24 +481,30 @@ void TestTreeModel::removeTestRootNodes() #ifdef WITH_TESTS // we're inside tests - so use some internal knowledge to make testing easier -TestTreeItem *qtRootNode() +static TestTreeItem *qtRootNode() { return TestFrameworkManager::instance()->rootNodeForTestFramework( Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix("QtTest")); } -TestTreeItem *quickRootNode() +static TestTreeItem *quickRootNode() { return TestFrameworkManager::instance()->rootNodeForTestFramework( Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix("QtQuickTest")); } -TestTreeItem *gtestRootNode() +static TestTreeItem *gtestRootNode() { return TestFrameworkManager::instance()->rootNodeForTestFramework( Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix("GTest")); } +static TestTreeItem *boostTestRootNode() +{ + return TestFrameworkManager::instance()->rootNodeForTestFramework( + Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix("Boost")); +} + int TestTreeModel::autoTestsCount() const { TestTreeItem *rootNode = qtRootNode(); @@ -570,6 +576,25 @@ QMultiMap<QString, int> TestTreeModel::gtestNamesAndSets() const } return result; } + +int TestTreeModel::boostTestNamesCount() const +{ + TestTreeItem *rootNode = boostTestRootNode(); + return rootNode ? rootNode->childCount() : 0; +} + +QMultiMap<QString, int> TestTreeModel::boostTestSuitesAndTests() const +{ + QMultiMap<QString, int> result; + + if (TestTreeItem *rootNode = boostTestRootNode()) { + rootNode->forFirstLevelChildren([&result](TestTreeItem *child) { + result.insert(child->name(), child->childCount()); + }); + } + return result; +} + #endif /***************************** Sort/Filter Model **********************************/ diff --git a/src/plugins/autotest/testtreemodel.h b/src/plugins/autotest/testtreemodel.h index 2945765d47..895a9a582e 100644 --- a/src/plugins/autotest/testtreemodel.h +++ b/src/plugins/autotest/testtreemodel.h @@ -56,7 +56,7 @@ public: bool hasTests() const; QList<TestConfiguration *> getAllTestCases() const; QList<TestConfiguration *> getSelectedTests() const; - QList<TestConfiguration *> getTestsForFile(const Utils::FileName &fileName) const; + QList<TestConfiguration *> getTestsForFile(const Utils::FilePath &fileName) const; QList<TestTreeItem *> testItemsByName(const QString &testName); void syncTestFrameworks(); void rebuild(const QList<Core::Id> &frameworkIds); @@ -70,6 +70,8 @@ public: int dataTagsCount() const; int gtestNamesCount() const; QMultiMap<QString, int> gtestNamesAndSets() const; + int boostTestNamesCount() const; + QMultiMap<QString, int> boostTestSuitesAndTests() const; #endif void markAllForRemoval(); diff --git a/src/plugins/autotest/unit_test/simple_boost/simple_boost.pro b/src/plugins/autotest/unit_test/simple_boost/simple_boost.pro new file mode 100644 index 0000000000..f0f3a156bb --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/simple_boost.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += src tests diff --git a/src/plugins/autotest/unit_test/simple_boost/simple_boost.qbs b/src/plugins/autotest/unit_test/simple_boost/simple_boost.qbs new file mode 100644 index 0000000000..413f4f4971 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/simple_boost.qbs @@ -0,0 +1,5 @@ +import qbs +Project { + name: "SimpleBoost" + references: [ "src/src.qbs", "tests/tests.qbs" ] +} diff --git a/src/plugins/autotest/unit_test/simple_boost/src/main.cpp b/src/plugins/autotest/unit_test/simple_boost/src/main.cpp new file mode 100644 index 0000000000..bc90974049 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/src/main.cpp @@ -0,0 +1,6 @@ +#include <iostream> +int main() +{ + std::cout << "Hello BoostWorld!\n"; + return 0; +} diff --git a/src/plugins/autotest/unit_test/simple_boost/src/src.pro b/src/plugins/autotest/unit_test/simple_boost/src/src.pro new file mode 100644 index 0000000000..0393f7894b --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/src/src.pro @@ -0,0 +1,4 @@ +TEMPLATE = app +CONFIG -= qt app_bundle +CONFIG += console +SOURCES += main.cpp diff --git a/src/plugins/autotest/unit_test/simple_boost/src/src.qbs b/src/plugins/autotest/unit_test/simple_boost/src/src.qbs new file mode 100644 index 0000000000..5b24d9d217 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/src/src.qbs @@ -0,0 +1,6 @@ +import qbs +CppApplication { + type: "application" + name: "HelloBoost application" + files: [ "main.cpp" ] +} diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/deco/deco.pro b/src/plugins/autotest/unit_test/simple_boost/tests/deco/deco.pro new file mode 100644 index 0000000000..0a69f075c2 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/deco/deco.pro @@ -0,0 +1,8 @@ +TEMPLATE = app +CONFIG -= qt app_bundle +CONFIG += console +SOURCES += main.cpp +HEADERS += enab.h + +isEmpty(BOOST_INCLUDE_DIR):BOOST_INCLUDE_DIR=$$(BOOST_INCLUDE_DIR) +!isEmpty(BOOST_INCLUDE_DIR): INCLUDEPATH *= $$BOOST_INCLUDE_DIR diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/deco/deco.qbs b/src/plugins/autotest/unit_test/simple_boost/tests/deco/deco.qbs new file mode 100644 index 0000000000..1f53178c05 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/deco/deco.qbs @@ -0,0 +1,11 @@ +import qbs +import qbs.File +CppApplication { + name: "Decorators Test" + type: "application" + Properties { + condition: project.boostIncDir && File.exists(project.boostIncDir) + cpp.includePaths: [project.boostIncDir]; + } + files: [ "enab.h", "main.cpp" ] +} diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/deco/enab.h b/src/plugins/autotest/unit_test/simple_boost/tests/deco/enab.h new file mode 100644 index 0000000000..2090c539eb --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/deco/enab.h @@ -0,0 +1,23 @@ +#pragma once +#include <boost/test/included/unit_test.hpp> + +namespace utf = boost::unit_test; + +BOOST_AUTO_TEST_SUITE(Suite1, * utf::disabled()) + BOOST_AUTO_TEST_CASE(test1) + { + BOOST_TEST(1 != 1); + } + BOOST_AUTO_TEST_CASE(Test2, * utf::enabled()) + { + BOOST_TEST(2 != 2); + } + BOOST_AUTO_TEST_CASE(TestIo, * utf::enable_if<true>()) + { + BOOST_TEST(3 != 3); + } + BOOST_AUTO_TEST_CASE(TestDb, * utf::enable_if<false>()) + { + BOOST_TEST(4 != 4); + } +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/deco/main.cpp b/src/plugins/autotest/unit_test/simple_boost/tests/deco/main.cpp new file mode 100644 index 0000000000..aef3d912f3 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/deco/main.cpp @@ -0,0 +1,21 @@ +#define BOOST_TEST_MODULE Suites and Decorators +#include <boost/test/included/unit_test.hpp> +#include "enab.h" +using boost::unit_test::label; + +BOOST_AUTO_TEST_CASE(testWithout1, *label("WO")) { BOOST_TEST (true); } +BOOST_AUTO_TEST_CASE(testWithout2, *label("WO")) { BOOST_TEST (false); } +BOOST_AUTO_TEST_SUITE(SuiteOuter) + BOOST_AUTO_TEST_SUITE(SuiteInner1) + BOOST_AUTO_TEST_CASE(Test1) { BOOST_TEST (true); } + BOOST_AUTO_TEST_CASE(Test2) { BOOST_TEST (true); } + BOOST_AUTO_TEST_SUITE_END() + BOOST_AUTO_TEST_SUITE(SuiteInner2, *boost::unit_test::disabled()) + BOOST_AUTO_TEST_CASE(Test1, *label("I2")) { BOOST_TEST (false); } + BOOST_AUTO_TEST_CASE(Test2, *label("I2")) { BOOST_TEST (false); } + BOOST_AUTO_TEST_SUITE_END() + BOOST_AUTO_TEST_CASE(Test1, *label("O1")) { BOOST_TEST (true); } + BOOST_AUTO_TEST_CASE(Test2) { BOOST_TEST (true); } + BOOST_AUTO_TEST_CASE(Test2A) { BOOST_TEST (true); } +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/fix/fix.cpp b/src/plugins/autotest/unit_test/simple_boost/tests/fix/fix.cpp new file mode 100644 index 0000000000..54325e5f25 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/fix/fix.cpp @@ -0,0 +1,18 @@ +#define BOOST_TEST_MODULE fixture example +#include <boost/test/included/unit_test.hpp> + +struct F { + F() : i( 0 ) { BOOST_TEST_MESSAGE( "setup fixture" ); } + ~F() { BOOST_TEST_MESSAGE( "teardown fixture" ); } + int i; +}; + +BOOST_FIXTURE_TEST_CASE( test_case1, F ) +{ + BOOST_TEST( i++ == 1 ); +} + +BOOST_FIXTURE_TEST_CASE( test_case2, F, * boost::unit_test::disabled() ) +{ + BOOST_CHECK_EQUAL( i, 1 ); +} diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/fix/fix.pro b/src/plugins/autotest/unit_test/simple_boost/tests/fix/fix.pro new file mode 100644 index 0000000000..f070f564b0 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/fix/fix.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG -= qt app_bundle +CONFIG += console +SOURCES += fix.cpp + +isEmpty(BOOST_INCLUDE_DIR):BOOST_INCLUDE_DIR=$$(BOOST_INCLUDE_DIR) +!isEmpty(BOOST_INCLUDE_DIR): INCLUDEPATH *= $$BOOST_INCLUDE_DIR diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/fix/fix.qbs b/src/plugins/autotest/unit_test/simple_boost/tests/fix/fix.qbs new file mode 100644 index 0000000000..5aa62d7888 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/fix/fix.qbs @@ -0,0 +1,11 @@ +import qbs +import qbs.File +CppApplication { + name: "Fixture Test" + type: "application" + Properties { + condition: project.boostIncDir && File.exists(project.boostIncDir) + cpp.includePaths: [project.boostIncDir]; + } + files: [ "fix.cpp" ] +} diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/params/main.cpp b/src/plugins/autotest/unit_test/simple_boost/tests/params/main.cpp new file mode 100644 index 0000000000..b0821844db --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/params/main.cpp @@ -0,0 +1,41 @@ +#include <boost/test/included/unit_test.hpp> +#include <boost/test/parameterized_test.hpp> +#include <boost/bind.hpp> + +using namespace boost::unit_test; + +class TestClass +{ +public: + void testMethod() + { + BOOST_TEST( true ); + } +}; + +void freeTestFunction() +{ + BOOST_TEST( true ); +} + +void freeTestFunction2(int i) +{ + BOOST_TEST( i < 4 ); +} + +test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) +{ + boost::shared_ptr<TestClass> tester( new TestClass ); + + framework::master_test_suite(). + add( BOOST_TEST_CASE( boost::bind( &TestClass::testMethod, tester ))); + + framework::master_test_suite(). + add( BOOST_TEST_CASE( &freeTestFunction) ); + + int params[] = {1, 2, 3, 4, 5, 6}; + framework::master_test_suite(). + add( BOOST_PARAM_TEST_CASE( &freeTestFunction2, params, params + 6) ); + + return nullptr; +} diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/params/params.pro b/src/plugins/autotest/unit_test/simple_boost/tests/params/params.pro new file mode 100644 index 0000000000..efea50f870 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/params/params.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG -= qt app_bundle +CONFIG += console +SOURCES += main.cpp + +isEmpty(BOOST_INCLUDE_DIR):BOOST_INCLUDE_DIR=$$(BOOST_INCLUDE_DIR) +!isEmpty(BOOST_INCLUDE_DIR): INCLUDEPATH *= $$BOOST_INCLUDE_DIR diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/params/params.qbs b/src/plugins/autotest/unit_test/simple_boost/tests/params/params.qbs new file mode 100644 index 0000000000..5e4633cdb1 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/params/params.qbs @@ -0,0 +1,11 @@ +import qbs +import qbs.File +CppApplication { + name: "Using Test Functions" + type: "application" + Properties { + condition: project.boostIncDir && File.exists(project.boostIncDir) + cpp.includePaths: [project.boostIncDir]; + } + files: [ "main.cpp" ] +} diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/tests.pro b/src/plugins/autotest/unit_test/simple_boost/tests/tests.pro new file mode 100644 index 0000000000..160045bd75 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/tests.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += fix params deco diff --git a/src/plugins/autotest/unit_test/simple_boost/tests/tests.qbs b/src/plugins/autotest/unit_test/simple_boost/tests/tests.qbs new file mode 100644 index 0000000000..2033b5de2e --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_boost/tests/tests.qbs @@ -0,0 +1,11 @@ +import qbs +import qbs.Environment +Project { + name: "Tests" + property string boostIncDir: { + if (typeof Environment.getEnv("BOOST_INCLUDE_DIR") !== 'undefined') + return Environment.getEnv("BOOST_INCLUDE_DIR"); + return undefined; + } + references: [ "deco/deco.qbs", "fix/fix.qbs", "params/params.qbs" ] +} diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/common/functions.js b/src/plugins/autotest/unit_test/simple_gt/tests/common/functions.js index 8ce7f31ad9..89cd648a31 100644 --- a/src/plugins/autotest/unit_test/simple_gt/tests/common/functions.js +++ b/src/plugins/autotest/unit_test/simple_gt/tests/common/functions.js @@ -1,8 +1,8 @@ -var FileInfo = loadExtension("qbs.FileInfo") +var FileInfo = require("qbs.FileInfo") -function getGTestDir(str) { +function getGTestDir(Qbs, str) { if (!str) { - if (qbs.hostOS.contains("linux")) + if (Qbs.hostOS.contains("linux")) return "/usr/include/gtest"; } else { return FileInfo.joinPaths(str, "googletest"); @@ -10,9 +10,9 @@ function getGTestDir(str) { return ""; } -function getGMockDir(str) { +function getGMockDir(Qbs, str) { if (!str) { - if (qbs.hostOS.contains("linux")) + if (Qbs.hostOS.contains("linux")) return "/usr/include/gmock"; } else { return FileInfo.joinPaths(str, "googlemock"); @@ -20,29 +20,29 @@ function getGMockDir(str) { return ""; } -function getGTestAll(str) { - var gtest = getGTestDir(str); +function getGTestAll(Qbs, str) { + var gtest = getGTestDir(Qbs, str); if (!gtest) return []; return [FileInfo.joinPaths(gtest, "src/gtest-all.cc")]; } -function getGMockAll(str) { - var gmock = getGMockDir(str); +function getGMockAll(Qbs, str) { + var gmock = getGMockDir(Qbs, str); if (!gmock) return []; return [FileInfo.joinPaths(gmock, "src/gmock-all.cc")]; } -function getGTestIncludes(str) { - var gtest = getGTestDir(str); +function getGTestIncludes(Qbs, str) { + var gtest = getGTestDir(Qbs, str); if (!gtest) return []; return [gtest, FileInfo.joinPaths(gtest, "include")]; } -function getGMockIncludes(str) { - var mock = getGMockDir(str); +function getGMockIncludes(Qbs, str) { + var mock = getGMockDir(Qbs, str); if (!mock) return []; return [mock, FileInfo.joinPaths(mock, "include")]; diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt1/gt1.qbs b/src/plugins/autotest/unit_test/simple_gt/tests/gt1/gt1.qbs index f560bb41e6..ac59d83da8 100644 --- a/src/plugins/autotest/unit_test/simple_gt/tests/gt1/gt1.qbs +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt1/gt1.qbs @@ -7,8 +7,8 @@ CppApplication { type: "application" name: "googletest1" - property string gtestDir: googleCommon.getGTestDir(project.googletestDir) - property string gmockDir: googleCommon.getGMockDir(project.googletestDir) + property string gtestDir: googleCommon.getGTestDir(qbs, project.googletestDir) + property string gmockDir: googleCommon.getGMockDir(qbs, project.googletestDir) condition: { if (File.exists(gtestDir) && File.exists(gmockDir)) @@ -19,8 +19,8 @@ CppApplication { return false; } - cpp.includePaths: [].concat(googleCommon.getGTestIncludes(project.googletestDir)) - .concat(googleCommon.getGMockIncludes(project.googletestDir)) + cpp.includePaths: [].concat(googleCommon.getGTestIncludes(qbs, project.googletestDir)) + .concat(googleCommon.getGMockIncludes(qbs, project.googletestDir)) cpp.cxxLanguageVersion: "c++11" cpp.defines: ["GTEST_LANG_CXX11"] @@ -30,6 +30,6 @@ CppApplication { // own stuff "further.cpp", "main.cpp", - ].concat(googleCommon.getGTestAll(project.googletestDir)) - .concat(googleCommon.getGMockAll(project.googletestDir)) + ].concat(googleCommon.getGTestAll(qbs, project.googletestDir)) + .concat(googleCommon.getGMockAll(qbs, project.googletestDir)) } diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt2/gt2.qbs b/src/plugins/autotest/unit_test/simple_gt/tests/gt2/gt2.qbs index b0a898103f..98fdc1e4c9 100644 --- a/src/plugins/autotest/unit_test/simple_gt/tests/gt2/gt2.qbs +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt2/gt2.qbs @@ -7,8 +7,8 @@ CppApplication { type: "application" name: "googletest2" - property string gtestDir: googleCommon.getGTestDir(project.googletestDir) - property string gmockDir: googleCommon.getGMockDir(project.googletestDir) + property string gtestDir: googleCommon.getGTestDir(qbs, project.googletestDir) + property string gmockDir: googleCommon.getGMockDir(qbs, project.googletestDir) Depends { name: "Qt.core" } @@ -21,8 +21,8 @@ CppApplication { return false; } - cpp.includePaths: [].concat(googleCommon.getGTestIncludes(project.googletestDir)) - .concat(googleCommon.getGMockIncludes(project.googletestDir)) + cpp.includePaths: [].concat(googleCommon.getGTestIncludes(qbs, project.googletestDir)) + .concat(googleCommon.getGMockIncludes(qbs, project.googletestDir)) cpp.cxxLanguageVersion: "c++11" cpp.defines: ["GTEST_LANG_CXX11"] @@ -31,6 +31,6 @@ CppApplication { // own stuff "queuetest.h", "main.cpp", - ].concat(googleCommon.getGTestAll(project.googletestDir)) - .concat(googleCommon.getGMockAll(project.googletestDir)) + ].concat(googleCommon.getGTestAll(qbs, project.googletestDir)) + .concat(googleCommon.getGMockAll(qbs, project.googletestDir)) } diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt2/queuetest.h b/src/plugins/autotest/unit_test/simple_gt/tests/gt2/queuetest.h index 7fb47e8e62..815db2bab2 100644 --- a/src/plugins/autotest/unit_test/simple_gt/tests/gt2/queuetest.h +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt2/queuetest.h @@ -25,6 +25,7 @@ #pragma once +#include <gtest/gtest.h> #include <QQueue> class QueueTest : public ::testing::Test diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt3/gt3.qbs b/src/plugins/autotest/unit_test/simple_gt/tests/gt3/gt3.qbs index 2bd250a588..e41b820594 100644 --- a/src/plugins/autotest/unit_test/simple_gt/tests/gt3/gt3.qbs +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt3/gt3.qbs @@ -7,8 +7,8 @@ CppApplication { type: "application" name: "googletest3" - property string gtestDir: googleCommon.getGTestDir(project.googletestDir) - property string gmockDir: googleCommon.getGMockDir(project.googletestDir) + property string gtestDir: googleCommon.getGTestDir(qbs, project.googletestDir) + property string gmockDir: googleCommon.getGMockDir(qbs, project.googletestDir) Depends { name: "Qt.core" } @@ -21,8 +21,8 @@ CppApplication { return false; } - cpp.includePaths: [].concat(googleCommon.getGTestIncludes(project.googletestDir)) - .concat(googleCommon.getGMockIncludes(project.googletestDir)) + cpp.includePaths: [].concat(googleCommon.getGTestIncludes(qbs, project.googletestDir)) + .concat(googleCommon.getGMockIncludes(qbs, project.googletestDir)) cpp.cxxLanguageVersion: "c++11" cpp.defines: ["GTEST_LANG_CXX11"] @@ -31,6 +31,6 @@ CppApplication { // own stuff "dummytest.h", "main.cpp", - ].concat(googleCommon.getGTestAll(project.googletestDir)) - .concat(googleCommon.getGMockAll(project.googletestDir)) + ].concat(googleCommon.getGTestAll(qbs, project.googletestDir)) + .concat(googleCommon.getGMockAll(qbs, project.googletestDir)) } diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt3/main.cpp b/src/plugins/autotest/unit_test/simple_gt/tests/gt3/main.cpp index 4f048fa177..a7d8591512 100644 --- a/src/plugins/autotest/unit_test/simple_gt/tests/gt3/main.cpp +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt3/main.cpp @@ -56,8 +56,8 @@ static QMap<const char *, QStringList> testValuesSec = { }, }; -INSTANTIATE_TEST_CASE_P(First, DummyTest, ::testing::ValuesIn(testValues.keys())); -INSTANTIATE_TEST_CASE_P(Second, DummyTest, ::testing::ValuesIn(testValuesSec.keys())); +INSTANTIATE_TEST_SUITE_P(First, DummyTest, ::testing::ValuesIn(testValues.keys())); +INSTANTIATE_TEST_SUITE_P(Second, DummyTest, ::testing::ValuesIn(testValuesSec.keys())); TEST_P(DummyTest, Easy) { |