diff --git a/src/plugins/autotest/CMakeLists.txt b/src/plugins/autotest/CMakeLists.txt index e5a6b98f3e0..fea6448ead8 100644 --- a/src/plugins/autotest/CMakeLists.txt +++ b/src/plugins/autotest/CMakeLists.txt @@ -11,6 +11,16 @@ add_qtc_plugin(AutoTest 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 diff --git a/src/plugins/autotest/autotest.pro b/src/plugins/autotest/autotest.pro index d8af2b75c76..22e2b594c9f 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 b25a27ce9cf..9d31d536468 100644 --- a/src/plugins/autotest/autotest.qbs +++ b/src/plugins/autotest/autotest.qbs @@ -101,6 +101,13 @@ QtcPlugin { ] } + Group { + name: "Boost Test framework files" + files: [ + "boost/*" + ] + } + Group { name: "Test sources" condition: qtc.testsEnabled diff --git a/src/plugins/autotest/autotestplugin.cpp b/src/plugins/autotest/autotestplugin.cpp index 2c367828443..72aa8854682 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 #include @@ -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); diff --git a/src/plugins/autotest/boost/boostcodeparser.cpp b/src/plugins/autotest/boost/boostcodeparser.cpp new file mode 100644 index 00000000000..43af677159b --- /dev/null +++ b/src/plugins/autotest/boost/boostcodeparser.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** 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 +#include +#include + +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_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 + 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 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; + } + 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 { + 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 + } + } + // TODO.. fixture, label, 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 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 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 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 00000000000..23fd4b3c030 --- /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 +#include +#include +#include + +#include + +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 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 00000000000..068a76245ad --- /dev/null +++ b/src/plugins/autotest/boost/boosttestconfiguration.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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 getBoostSettings() +{ + const Core::Id id = Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix( + BoostTest::Constants::FRAMEWORK_NAME); + TestFrameworkManager *manager = TestFrameworkManager::instance(); + return qSharedPointerCast(manager->settingsForTestFramework(id)); +} + +TestOutputReader *BoostTestConfiguration::outputReader(const QFutureInterface &fi, + QProcess *app) const +{ + auto settings = getBoostSettings(); + return new BoostTestOutputReader(fi, app, buildDirectory(), projectFile(), + settings->logLevel, settings->reportLevel); +} + +static QStringList filterInterfering(const QStringList &provided, QStringList *omitted) +{ + // TODO complete these... + const QSet knownInterfering { "--log_level=", + "--report_level=", + "--color_output=", "--no_color_output", + "--catch_system_errors", "--detect_fp_exceptions", + "--detect_memory_leaks", "--random=" + }; + QSet allowed = Utils::filtered(provided.toSet(), [&knownInterfering](const QString &arg) { + return Utils::allOf(knownInterfering, [&arg](const QString &interfering) { + return !arg.startsWith(interfering); + }); + }); + + // TODO handle single letter options correctly + if (omitted) { + QSet providedSet = provided.toSet(); + providedSet.subtract(allowed); + omitted->append(providedSet.toList()); + } + return allowed.toList(); +} + +QStringList BoostTestConfiguration::argumentsForTestRunner(QStringList *omitted) const +{ + QStringList arguments; + if (AutotestPlugin::settings()->processArgs) { + arguments << filterInterfering(runnable().commandLineArguments.split( + ' ', QString::SkipEmptyParts), omitted); + } + + // TODO improve the test case gathering and arguments building to avoid too long command lines + for (const QString &test : testCases()) + arguments << "-t" << test; + + auto boostSettings = getBoostSettings(); + arguments << "-l" << BoostTestSettings::logLevelToOption(boostSettings->logLevel); + arguments << "-r" << BoostTestSettings::reportLevelToOption(boostSettings->reportLevel); + + if (boostSettings->randomize) + arguments << QString("--random=").append(boostSettings->seed); + + if (boostSettings->systemErrors) + arguments << "-s"; + if (boostSettings->fpExceptions) + arguments << "--detect_fp_exceptions"; + if (!boostSettings->memLeaks) + arguments << "--detect_memory_leaks=0"; + return arguments; +} + +Utils::Environment BoostTestConfiguration::filteredEnvironment(const Utils::Environment &original) const +{ + // TODO complete these.. + const QStringList interfering{"BOOST_TEST_LOG_FORMAT", "BOOST_TEST_REPORT_FORMAT", + "BOOST_TEST_COLOR_OUTPUT", "BOOST_TEST_CATCH_SYSTEM_ERRORS", + "BOOST_TEST_DETECT_FP_EXCEPTIONS", "BOOST_TEST_RANDOM", + "BOOST_TEST_DETECT_MEMORY_LEAK"}; + + Utils::Environment result = original; + for (const QString &key : interfering) + 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 00000000000..e792fe21d51 --- /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 &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 00000000000..2c993c1d4f6 --- /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 + +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 00000000000..a6b75151543 --- /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 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 00000000000..c9db343deba --- /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 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 00000000000..640d8004dc2 --- /dev/null +++ b/src/plugins/autotest/boost/boosttestoutputreader.cpp @@ -0,0 +1,423 @@ +/**************************************************************************** +** +** 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 + +#include +#include +#include +#include + +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 &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::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("([^\\s]+):.*"); + 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); + } // else TODO + + result->setDescription(m_description); + result->setResult(m_result); + reportResult(TestResultPtr(result)); + m_result = ResultType::Invalid; +} + +void BoostTestOutputReader::handleMessageMatch(const QRegularExpressionMatch &match) +{ + if (m_result != ResultType::Invalid) + sendCompleteInformation(); + + 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; + 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; + m_description = content; + } +} + +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)$"); + 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; + } + + // should summary get reported unconditionally? + match = summaryPreamble.match(line); + if (match.hasMatch()) { + createAndReportResult(match.captured(0), ResultType::MessageInfo); + return; + } + + match = summaryDetail.match(line); + if (match.hasMatch()) { + createAndReportResult(match.captured(0), ResultType::MessageInfo); + 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); + 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)); + if (m_testCaseCount != -1) + txt.append(' ').append(tr("%1 tests passed.").arg(m_testCaseCount - failed)); + result->setDescription(txt); + result->setResult(ResultType::MessageInfo); // TODO report similar to disabled tests + reportResult(TestResultPtr(result)); + 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); // TODO report similar to disabled tests + reportResult(TestResultPtr(result)); + 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); + + // TODO find corresponding TestTreeItem and set filename/line + return TestResultPtr(result); +} + +void BoostTestOutputReader::onFinished(int exitCode, QProcess::ExitStatus /*exitState*/) { + // 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 + // but exit code 0 can be forced with an option - what todo in that case? + 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 00000000000..e16c1fb9963 --- /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 &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 00000000000..b198d135a95 --- /dev/null +++ b/src/plugins/autotest/boost/boosttestparser.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** 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 + +#include +#include +#include + +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") +}; + +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 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 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 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 00000000000..678b859cc99 --- /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 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 00000000000..cf6d6e7b411 --- /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(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 00000000000..b27dda16e87 --- /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 00000000000..7b1fc855ed6 --- /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 00000000000..8e10af856f4 --- /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 00000000000..e60f5f4643b --- /dev/null +++ b/src/plugins/autotest/boost/boosttestsettingspage.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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 + +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 settings, + const ITestFramework *framework) + : ITestSettingsPage(framework), + m_settings(qSharedPointerCast(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 00000000000..5e0e65f7b95 --- /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 + +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 settings, + const ITestFramework *framework); + + QWidget *widget() override; + void apply() override; + +private: + QSharedPointer m_settings; + QPointer 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 00000000000..1596cd7d41d --- /dev/null +++ b/src/plugins/autotest/boost/boosttestsettingspage.ui @@ -0,0 +1,163 @@ + + + BoostSettingsPage + + + + 0 + 0 + 309 + 284 + + + + Form + + + + + + + + + + + + Log format: + + + + + + + + + + + + + + Report level: + + + + + + + + + + + + + + Randomize execution order. + + + Randomize + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Seed: + + + + + + + false + + + A seed of 0 means no randomization. A value of 1 uses the current time any other value is used as random seed generator. + + + 65535 + + + + + + + + + Catch or ignore system errors. + + + Catch system errors + + + + + + + Enable floating point exception traps. + + + Floating point exceptions + + + + + + + Enable memory leak detection. + + + Detect memory leaks + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 84 + + + + + + + + + diff --git a/src/plugins/autotest/boost/boosttesttreeitem.cpp b/src/plugins/autotest/boost/boosttesttreeitem.cpp new file mode 100644 index 00000000000..ac1e900b47c --- /dev/null +++ b/src/plugins/autotest/boost/boosttesttreeitem.cpp @@ -0,0 +1,354 @@ +/**************************************************************************** +** +** 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 +#include + +#include +#include + +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(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(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(other); + result = findChildByNameStateAndFile(bOther->name(), bOther->state(), + bOther->proFile()); + } + return (result && result->type() == otherType) ? result : nullptr; + } + case GroupNode: { + auto bOther = static_cast(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(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(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 BoostTestTreeItem::getAllTestConfigurations() const +{ + QList result; + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (!project || type() != Root) + return result; + + struct BoostTestCases { + int testCases; + QSet internalTargets; + }; + + // we only need the unique project files (and number of test cases for the progress indicator) + QHash testsPerProjectfile; + forAllChildren([&testsPerProjectfile](TreeItem *it){ + auto item = static_cast(it); + if (item->type() != TestSuite) + return; + int funcChildren = 0; + item->forAllChildren([&funcChildren](TreeItem *child){ + if (static_cast(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 BoostTestTreeItem::getSelectedTestConfigurations() const +{ + QList result; + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (!project || type() != Root) + return result; + + struct BoostTestCases { + QStringList testCases; + QSet internalTargets; + }; + + QHash testCasesForProjectFile; + forAllChildren([&testCasesForProjectFile](TreeItem *it){ + auto item = static_cast(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 = handleSpecialFunctionNames(item->name()); + 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(child)) { + if (boostItem->enabled()) { + QString tcName = handleSpecialFunctionNames(boostItem->name()); + if (boostItem->type() == TestSuite) // execute everything below a suite + tcName.append("/*"); + testCases.append(boostItem->prependWithParentsSuitePaths(tcName)); + } + } + }); + } else { + testCases.append(prependWithParentsSuitePaths(handleSpecialFunctionNames(name()))); + } + + 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(testConfiguration()); + if (config) + config->setRunMode(TestRunMode::Debug); + return config; +} + +QString BoostTestTreeItem::nameSuffix() const +{ + static QString markups[] = {QCoreApplication::translate("BoostTestTreeItem", "parameterized"), + QCoreApplication::translate("BoostTestTreeItem", "fixture")}; + QString suffix; + if (m_state & Parameterized) + suffix = QString(" [") + markups[0]; + if (m_state & Fixture) + suffix += (suffix.isEmpty() ? QString(" [") : QString(", ")) + markups[1]; + 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(parent)->enabled(); + + return true; +} + +TestTreeItem *BoostTestTreeItem::findChildByNameStateAndFile(const QString &name, + BoostTestTreeItem::TestStates state, + const QString &proFile) const +{ + return static_cast( + findAnyChild([name, state, proFile](const Utils::TreeItem *other){ + const BoostTestTreeItem *boostItem = static_cast(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 00000000000..31d4dd00456 --- /dev/null +++ b/src/plugins/autotest/boost/boosttesttreeitem.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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, + }; + 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 getAllTestConfigurations() const override; + QList 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 BoostTestInfoList; + +class BoostTestCodeLocationAndType : public TestCodeLocationAndType +{ +public: + BoostTestTreeItem::TestStates m_state; + BoostTestInfoList m_suitesState; +}; + +typedef QVector BoostTestCodeLocationList; + + +} // namespace Internal +} // namespace Autotest