diff --git a/share/qtcreator/templates/wizards/autotest/auto.pro b/share/qtcreator/templates/wizards/autotest/auto.pro new file mode 100644 index 00000000000..b39fef654c3 --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/auto.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS += %TestCaseName:l% diff --git a/share/qtcreator/templates/wizards/autotest/autotest_24.png b/share/qtcreator/templates/wizards/autotest/autotest_24.png new file mode 100644 index 00000000000..3f52e9bf08f Binary files /dev/null and b/share/qtcreator/templates/wizards/autotest/autotest_24.png differ diff --git a/share/qtcreator/templates/wizards/autotest/main.cpp b/share/qtcreator/templates/wizards/autotest/main.cpp new file mode 100644 index 00000000000..3ff500decd6 --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/main.cpp @@ -0,0 +1,18 @@ +@if "%RequireGUI%" == "true" +#include +@else +#include +@endif + +// add necessary includes here + +int main(int argc, char *argv[]) +{ +@if "%RequireGUI%" == "true" + QApplication a(argc, argv); +@else + QCoreApplication a(argc, argv); +@endif + + return a.exec(); +} diff --git a/share/qtcreator/templates/wizards/autotest/src.pro b/share/qtcreator/templates/wizards/autotest/src.pro new file mode 100644 index 00000000000..61fbe2a5bcf --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/src.pro @@ -0,0 +1,13 @@ +@if "%RequireGUI%" == "true" +QT += core gui +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets +@else +QT -= gui +CONFIG += console +CONFIG -= app_bundle +@endif + +TEMPLATE = app +TARGET = %ProjectName% + +SOURCES += main.%CppSourceSuffix% diff --git a/share/qtcreator/templates/wizards/autotest/tests.pro b/share/qtcreator/templates/wizards/autotest/tests.pro new file mode 100644 index 00000000000..f9277000081 --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/tests.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS += auto diff --git a/share/qtcreator/templates/wizards/autotest/tmp.pro b/share/qtcreator/templates/wizards/autotest/tmp.pro new file mode 100644 index 00000000000..1569bd45cb7 --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/tmp.pro @@ -0,0 +1,12 @@ +TEMPLATE = subdirs + +@if "%BuildTests%" == "always" +SUBDIRS += src \ + tests +@else +SUBDIRS += src + +CONFIG(debug, debug|release) { + SUBDIRS += tests +} +@endif diff --git a/share/qtcreator/templates/wizards/autotest/tst.pro b/share/qtcreator/templates/wizards/autotest/tst.pro new file mode 100644 index 00000000000..8aa61b767e7 --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/tst.pro @@ -0,0 +1,14 @@ +QT += testlib +@if "%RequireGUI%" == "false" +QT -= gui + +CONFIG += qt console warn_on depend_includepath testcase +CONFIG -= app_bundle +@else +QT += gui +CONFIG += qt warn_on depend_includepath testcase +@endif + +TEMPLATE = app + +SOURCES += tst_%TestCaseName:l%.%CppSourceSuffix% diff --git a/share/qtcreator/templates/wizards/autotest/tst_src.cpp b/share/qtcreator/templates/wizards/autotest/tst_src.cpp new file mode 100644 index 00000000000..65d49e9c18a --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/tst_src.cpp @@ -0,0 +1,58 @@ +#include +@if "%RequireApplication%" == "true" +#include +@endif + +// add necessary includes here + +class %TestCaseName% : public QObject +{ + Q_OBJECT + +public: + %TestCaseName%(); + ~%TestCaseName%(); + +private slots: +@if "%GenerateInitAndCleanup%" == "true" + void initTestCase(); + void cleanupTestCase(); +@endif + void test_case1(); + +}; + +%TestCaseName%::%TestCaseName%() +{ + +} + +%TestCaseName%::~%TestCaseName%() +{ + +} + +@if "%GenerateInitAndCleanup%" == "true" +void %TestCaseName%::initTestCase() +{ + +} + +void %TestCaseName%::cleanupTestCase() +{ + +} + +@endif +void %TestCaseName%::test_case1() +{ + +} + +@if "%RequireApplication%" == "true" +QTEST_MAIN(%TestCaseName%) +@else +QTEST_APPLESS_MAIN(%TestCaseName%) +@endif + +#include "tst_%TestCaseName:l%.moc" diff --git a/share/qtcreator/templates/wizards/autotest/wizard.xml b/share/qtcreator/templates/wizards/autotest/wizard.xml new file mode 100644 index 00000000000..12f0c1e0075 --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/wizard.xml @@ -0,0 +1,83 @@ + + + + + autotest_24.png + Creates a new project including auto test skeleton. + Auto Test; + Other Project + + + + + + + + + + + Project and Test Information + + + + GUI Application + + + + Test Case Name: + + + + Requires QApplication + + + + Generate initialization and cleanup code + + + + + + always + + + debug only + + + + Build auto tests + + + diff --git a/src/plugins/autotest/AutoTest.json.in b/src/plugins/autotest/AutoTest.json.in new file mode 100644 index 00000000000..a11c3b3f788 --- /dev/null +++ b/src/plugins/autotest/AutoTest.json.in @@ -0,0 +1,19 @@ +{ +\"Name\" : \"AutoTest\", +\"Version\" : \"$$QTCREATOR_VERSION\", +\"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", +\"Experimental\" : true, +\"Vendor\" : \"The Qt Company Ltd\", +\"Copyright\" : \"(C) 2015 The Qt Company Ltd\", +\"License\" : [ \"Commercial Usage\", +\"\", +\"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt 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.\", +\"\", +\"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.\" +], +\"Description\" : \"Auto Test plugin.\", +\"Url\" : \"http://www.qt.io\", +$$dependencyList +} diff --git a/src/plugins/autotest/autotest.pro b/src/plugins/autotest/autotest.pro new file mode 100644 index 00000000000..057060f2fac --- /dev/null +++ b/src/plugins/autotest/autotest.pro @@ -0,0 +1,63 @@ +TARGET = AutoTest +TEMPLATE = lib + +include(../../qtcreatorplugin.pri) +include(autotest_dependencies.pri) + +DEFINES += AUTOTEST_LIBRARY + +SOURCES += \ + testtreeview.cpp \ + testtreemodel.cpp \ + testtreeitem.cpp \ + testvisitor.cpp \ + testinfo.cpp \ + testcodeparser.cpp \ + autotestplugin.cpp \ + testrunner.cpp \ + testconfiguration.cpp \ + testresult.cpp \ + testresultspane.cpp \ + testresultmodel.cpp \ + testresultdelegate.cpp \ + testtreeitemdelegate.cpp \ + testsettings.cpp \ + testsettingspage.cpp \ + testnavigationwidget.cpp \ + testoutputreader.cpp + +HEADERS += \ + testtreeview.h \ + testtreemodel.h \ + testtreeitem.h \ + testvisitor.h \ + testinfo.h \ + testcodeparser.h \ + autotestplugin.h \ + autotest_global.h \ + autotest_utils.h \ + autotestconstants.h \ + testrunner.h \ + testconfiguration.h \ + testresult.h \ + testresultspane.h \ + testresultmodel.h \ + testresultdelegate.h \ + testtreeitemdelegate.h \ + testsettings.h \ + testsettingspage.h \ + testnavigationwidget.h \ + testoutputreader.h \ + autotesticons.h + +RESOURCES += \ + autotest.qrc + +FORMS += \ + testsettingspage.ui + +equals(TEST, 1) { + HEADERS += autotestunittests.h + SOURCES += autotestunittests.cpp + RESOURCES += autotestunittests.qrc +} diff --git a/src/plugins/autotest/autotest.qbs b/src/plugins/autotest/autotest.qbs new file mode 100644 index 00000000000..17b94e0c45e --- /dev/null +++ b/src/plugins/autotest/autotest.qbs @@ -0,0 +1,96 @@ +import qbs + +QtcCommercialPlugin { + name: "AutoTest" + + Depends { name: "Core" } + Depends { name: "CppTools" } + Depends { name: "CPlusPlus" } + Depends { name: "ProjectExplorer" } + Depends { name: "QmlJS" } + Depends { name: "QmlJSTools" } + Depends { name: "Utils" } + + pluginTestDepends: [ + "QbsProjectManager", + "QmakeProjectManager" + ] + + Depends { + name: "QtSupport" + condition: project.testsEnabled + } + + Depends { + name: "Qt.test" + condition: project.testsEnabled + } + + Depends { name: "Qt.widgets" } + + files: [ + "autotest.qrc", + "autotesticons.h", + "autotest_global.h", + "autotest_utils.h", + "autotestconstants.h", + "autotestplugin.cpp", + "autotestplugin.h", + "testcodeparser.cpp", + "testcodeparser.h", + "testconfiguration.cpp", + "testconfiguration.h", + "testinfo.cpp", + "testinfo.h", + "testnavigationwidget.cpp", + "testnavigationwidget.h", + "testresult.cpp", + "testresult.h", + "testresultdelegate.cpp", + "testresultdelegate.h", + "testresultmodel.cpp", + "testresultmodel.h", + "testresultspane.cpp", + "testresultspane.h", + "testrunner.cpp", + "testrunner.h", + "testsettings.cpp", + "testsettings.h", + "testsettingspage.cpp", + "testsettingspage.h", + "testsettingspage.ui", + "testtreeitem.cpp", + "testtreeitem.h", + "testtreeitemdelegate.cpp", + "testtreeitemdelegate.h", + "testtreemodel.cpp", + "testtreemodel.h", + "testtreeview.cpp", + "testtreeview.h", + "testvisitor.cpp", + "testvisitor.h", + "testoutputreader.cpp", + "testoutputreader.h", + ] + + Group { + name: "Test sources" + condition: project.testsEnabled + files: [ + "autotestunittests.cpp", + "autotestunittests.h", + "autotestunittests.qrc", + ] + } + + Group { + name: "Auto Test Wizard" + prefix: "../../shared/autotest/" + files: [ + "*" + ] + fileTags: [] + qbs.install: true + qbs.installDir: project.ide_data_path + "/templates/wizards/autotest" + } +} diff --git a/src/plugins/autotest/autotest.qrc b/src/plugins/autotest/autotest.qrc new file mode 100644 index 00000000000..698a86950e4 --- /dev/null +++ b/src/plugins/autotest/autotest.qrc @@ -0,0 +1,31 @@ + + + images/autotest.png + images/class.png + images/func.png + images/expand.png + images/expand@2x.png + images/collapse.png + images/collapse@2x.png + images/sort.png + images/sort@2x.png + images/leafsort.png + images/leafsort@2x.png + images/debug.png + images/fail.png + images/fatal.png + images/pass.png + images/skip.png + images/warn.png + images/xfail.png + images/xpass.png + images/blacklisted_fail.png + images/blacklisted_pass.png + images/benchmark.png + images/runselected_boxes.png + images/runselected_boxes@2x.png + images/runselected_tickmarks.png + images/runselected_tickmarks@2x.png + images/data.png + + diff --git a/src/plugins/autotest/autotest_dependencies.pri b/src/plugins/autotest/autotest_dependencies.pri new file mode 100644 index 00000000000..ab2e02f054d --- /dev/null +++ b/src/plugins/autotest/autotest_dependencies.pri @@ -0,0 +1,19 @@ +QTC_PLUGIN_NAME = AutoTest + +QTC_PLUGIN_DEPENDS += \ + coreplugin \ + projectexplorer \ + cpptools \ + qmljstools + +QTC_LIB_DEPENDS += \ + cplusplus \ + qmljs \ + utils + +QTC_TEST_DEPENDS += \ + qbsprojectmanager \ + qmakeprojectmanager \ + qtsupport + +#QTC_PLUGIN_RECOMMENDS += \ diff --git a/src/plugins/autotest/autotest_global.h b/src/plugins/autotest/autotest_global.h new file mode 100644 index 00000000000..03822d41315 --- /dev/null +++ b/src/plugins/autotest/autotest_global.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef AUTOTEST_GLOBAL_H +#define AUTOTEST_GLOBAL_H + +#include + +#if defined(AUTOTEST_LIBRARY) +# define AUTOTESTSHARED_EXPORT Q_DECL_EXPORT +#else +# define AUTOTESTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // AUTOTEST_GLOBAL_H + diff --git a/src/plugins/autotest/autotest_utils.h b/src/plugins/autotest/autotest_utils.h new file mode 100644 index 00000000000..ca2b38cea26 --- /dev/null +++ b/src/plugins/autotest/autotest_utils.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef AUTOTEST_UTILS_H +#define AUTOTEST_UTILS_H + +#include + +namespace Autotest { +namespace Internal { + +class TestUtils +{ +public: + static bool isGTestMacro(const QString ¯o) + { + static QStringList valid = { + QStringLiteral("TEST"), QStringLiteral("TEST_F"), QStringLiteral("TEST_P") + }; + return valid.contains(macro); + } + + static bool isGTestParameterized(const QString ¯o) + { + return macro == QStringLiteral("TEST_P"); + } + + static bool isQTestMacro(const QByteArray ¯o) + { + static QByteArrayList valid = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"}; + return valid.contains(macro); + } + + static bool isQuickTestMacro(const QByteArray ¯o) + { + static const QByteArrayList valid = {"QUICK_TEST_MAIN", "QUICK_TEST_OPENGL_MAIN"}; + return valid.contains(macro); + } +}; + +} // namespace Internal +} // namespace Autotest + +#endif // AUTOTEST_UTILS_H diff --git a/src/plugins/autotest/autotestconstants.h b/src/plugins/autotest/autotestconstants.h new file mode 100644 index 00000000000..5e9f3c56c4d --- /dev/null +++ b/src/plugins/autotest/autotestconstants.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef AUTOTESTCONSTANTS_H +#define AUTOTESTCONSTANTS_H + +#include + +namespace Autotest { +namespace Constants { + +const char ACTION_SCAN_ID[] = "AutoTest.ScanAction"; +const char ACTION_RUN_ALL_ID[] = "AutoTest.RunAll"; +const char ACTION_RUN_SELECTED_ID[] = "AutoTest.RunSelected"; +const char MENU_ID[] = "AutoTest.Menu"; +const char AUTOTEST_ID[] = "AutoTest.ATP"; +const char AUTOTEST_CONTEXT[] = "Auto Tests"; +const char TASK_INDEX[] = "AutoTest.Task.Index"; +const char TASK_PARSE[] = "AutoTest.Task.Parse"; +const char UNNAMED_QUICKTESTS[] = QT_TR_NOOP(""); +const char AUTOTEST_SETTINGS_CATEGORY[] = "ZY.Tests"; + +} // namespace Constants + +namespace Internal { + +enum TestType +{ + TestTypeQt, + TestTypeGTest +}; + +} // namespace Internal +} // namespace Autotest + +#endif // AUTOTESTCONSTANTS_H + diff --git a/src/plugins/autotest/autotesticons.h b/src/plugins/autotest/autotesticons.h new file mode 100644 index 00000000000..88c929dc977 --- /dev/null +++ b/src/plugins/autotest/autotesticons.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef AUTOTESTICONS_H +#define AUTOTESTICONS_H + +#include + +namespace Autotest { +namespace Icons { + +const Utils::Icon EXPAND({ + {QLatin1String(":/images/expand.png"), Utils::Theme::IconsBaseColor}}); +const Utils::Icon COLLAPSE({ + {QLatin1String(":/images/collapse.png"), Utils::Theme::IconsBaseColor}}); +const Utils::Icon SORT_ALPHABETICALLY({ + {QLatin1String(":/images/sort.png"), Utils::Theme::IconsBaseColor}}); +const Utils::Icon SORT_NATURALLY({ + {QLatin1String(":/images/leafsort.png"), Utils::Theme::IconsBaseColor}}); +const Utils::Icon RUN_SELECTED_OVERLAY({ + {QLatin1String(":/images/runselected_boxes.png"), Utils::Theme::BackgroundColorDark}, + {QLatin1String(":/images/runselected_tickmarks.png"), Utils::Theme::IconsBaseColor}}); + +} // namespace Icons +} // namespace Autotest + +#endif // AUTOTESTICONS_H diff --git a/src/plugins/autotest/autotestplugin.cpp b/src/plugins/autotest/autotestplugin.cpp new file mode 100644 index 00000000000..83f3be8da46 --- /dev/null +++ b/src/plugins/autotest/autotestplugin.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "autotestplugin.h" +#include "autotestconstants.h" +#include "testcodeparser.h" +#include "testrunner.h" +#include "testsettings.h" +#include "testsettingspage.h" +#include "testtreeitem.h" +#include "testtreeview.h" +#include "testtreemodel.h" +#include "testresultspane.h" +#include "testnavigationwidget.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#ifdef WITH_TESTS +#include "autotestunittests.h" +#endif + +using namespace Autotest::Internal; +using namespace Core; + +static AutotestPlugin *m_instance = 0; + +AutotestPlugin::AutotestPlugin() + : m_settings(new TestSettings) +{ + // needed to be used in QueuedConnection connects + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + + m_instance = this; +} + +AutotestPlugin::~AutotestPlugin() +{ + // Delete members + TestTreeModel *model = TestTreeModel::instance(); + delete model; + TestRunner *runner = TestRunner::instance(); + delete runner; +} + +AutotestPlugin *AutotestPlugin::instance() +{ + return m_instance; +} + +QSharedPointer AutotestPlugin::settings() const +{ + return m_settings; +} + +void AutotestPlugin::initializeMenuEntries() +{ + ActionContainer *menu = ActionManager::createMenu(Constants::MENU_ID); + menu->menu()->setTitle(tr("Tests")); + + QAction *action = new QAction(tr("Run &All Tests"), this); + Command *command = ActionManager::registerAction(action, Constants::ACTION_RUN_ALL_ID); + command->setDefaultKeySequence(QKeySequence(tr("Alt+Shift+T,Alt+A"))); + connect(action, &QAction::triggered, + this, &AutotestPlugin::onRunAllTriggered); + menu->addAction(command); + + action = new QAction(tr("&Run Selected Tests"), this); + command = ActionManager::registerAction(action, Constants::ACTION_RUN_SELECTED_ID); + command->setDefaultKeySequence(QKeySequence(tr("Alt+Shift+T,Alt+R"))); + connect(action, &QAction::triggered, + this, &AutotestPlugin::onRunSelectedTriggered); + menu->addAction(command); + + action = new QAction(tr("Re&scan Tests"), this); + command = ActionManager::registerAction(action, Constants::ACTION_SCAN_ID); + command->setDefaultKeySequence(QKeySequence(tr("Alt+Shift+T,Alt+S"))); + connect(action, &QAction::triggered, + TestTreeModel::instance()->parser(), &TestCodeParser::updateTestTree); + menu->addAction(command); + + ActionContainer *toolsMenu = ActionManager::actionContainer(Core::Constants::M_TOOLS); + toolsMenu->addMenu(menu); + connect(toolsMenu->menu(), &QMenu::aboutToShow, + this, &AutotestPlugin::updateMenuItemsEnabledState); +} + +bool AutotestPlugin::initialize(const QStringList &arguments, QString *errorString) +{ + Q_UNUSED(arguments) + Q_UNUSED(errorString) + + initializeMenuEntries(); + + m_settings->fromSettings(ICore::settings()); + addAutoReleasedObject(new TestSettingsPage(m_settings)); + addAutoReleasedObject(new TestNavigationWidgetFactory); + addAutoReleasedObject(TestResultsPane::instance()); + + return true; +} + +void AutotestPlugin::extensionsInitialized() +{ +} + +ExtensionSystem::IPlugin::ShutdownFlag AutotestPlugin::aboutToShutdown() +{ + return SynchronousShutdown; +} + +void AutotestPlugin::onRunAllTriggered() +{ + TestRunner *runner = TestRunner::instance(); + TestTreeModel *model = TestTreeModel::instance(); + runner->setSelectedTests(model->getAllTestCases()); + runner->prepareToRunTests(); +} + +void AutotestPlugin::onRunSelectedTriggered() +{ + TestRunner *runner = TestRunner::instance(); + TestTreeModel *model = TestTreeModel::instance(); + runner->setSelectedTests(model->getSelectedTests()); + runner->prepareToRunTests(); +} + +void AutotestPlugin::updateMenuItemsEnabledState() +{ + const bool enabled = !TestRunner::instance()->isTestRunning() + && TestTreeModel::instance()->parser()->state() == TestCodeParser::Idle; + const bool hasTests = TestTreeModel::instance()->hasTests(); + + ActionManager::command(Constants::ACTION_RUN_ALL_ID)->action()->setEnabled(enabled && hasTests); + ActionManager::command(Constants::ACTION_RUN_SELECTED_ID)->action()->setEnabled(enabled && hasTests); + ActionManager::command(Constants::ACTION_SCAN_ID)->action()->setEnabled(enabled); +} + +QList AutotestPlugin::createTestObjects() const +{ + QList tests; +#ifdef WITH_TESTS + tests << new AutoTestUnitTests(TestTreeModel::instance()); +#endif + return tests; +} diff --git a/src/plugins/autotest/autotestplugin.h b/src/plugins/autotest/autotestplugin.h new file mode 100644 index 00000000000..18ffbaf4ed4 --- /dev/null +++ b/src/plugins/autotest/autotestplugin.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef AUTOTESTPLUGIN_H +#define AUTOTESTPLUGIN_H + +#include "autotest_global.h" + +#include + +namespace Autotest { +namespace Internal { + +struct TestSettings; + +class AutotestPlugin : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "AutoTest.json") + +public: + AutotestPlugin(); + ~AutotestPlugin(); + + static AutotestPlugin *instance(); + + QSharedPointer settings() const; + + bool initialize(const QStringList &arguments, QString *errorString); + void extensionsInitialized(); + ShutdownFlag aboutToShutdown(); + +private: + bool checkLicense(); + void initializeMenuEntries(); + void onRunAllTriggered(); + void onRunSelectedTriggered(); + void updateMenuItemsEnabledState(); + QList createTestObjects() const; + const QSharedPointer m_settings; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // AUTOTESTPLUGIN_H + diff --git a/src/plugins/autotest/autotestunittests.cpp b/src/plugins/autotest/autotestunittests.cpp new file mode 100644 index 00000000000..6e58f877200 --- /dev/null +++ b/src/plugins/autotest/autotestunittests.cpp @@ -0,0 +1,243 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "autotestconstants.h" +#include "autotestunittests.h" +#include "testcodeparser.h" +#include "testtreemodel.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +using namespace Core; +using namespace ProjectExplorer; +using namespace Utils; + +namespace Autotest { +namespace Internal { + +AutoTestUnitTests::AutoTestUnitTests(TestTreeModel *model, QObject *parent) + : QObject(parent), + m_model(model), + m_tmpDir(0), + m_isQt4(false) +{ +} + +void AutoTestUnitTests::initTestCase() +{ + const QList allKits = KitManager::kits(); + if (allKits.count() != 1) + QSKIP("This test requires exactly one kit to be present"); + if (auto qtVersion = QtSupport::QtKitInformation::qtVersion(allKits.first())) + m_isQt4 = qtVersion->qtVersionString().startsWith(QLatin1Char('4')); + else + QSKIP("Could not figure out which Qt version is used for default kit."); + const ToolChain * const toolchain = ToolChainKitInformation::toolChain(allKits.first()); + if (!toolchain) + QSKIP("This test requires that there is a kit with a toolchain."); + + m_tmpDir = new CppTools::Tests::TemporaryCopiedDir(QLatin1String(":/unit_test")); +} + +void AutoTestUnitTests::cleanupTestCase() +{ + delete m_tmpDir; +} + +void AutoTestUnitTests::testCodeParser() +{ + QFETCH(QString, projectFilePath); + QFETCH(int, expectedAutoTestsCount); + QFETCH(int, expectedNamedQuickTestsCount); + QFETCH(int, expectedUnnamedQuickTestsCount); + QFETCH(int, expectedDataTagsCount); + + NavigationWidget *navigation = NavigationWidget::instance(); + navigation->activateSubWidget(Constants::AUTOTEST_ID); + + CppTools::Tests::ProjectOpenerAndCloser projectManager; + const CppTools::ProjectInfo projectInfo = projectManager.open(projectFilePath, true); + QVERIFY(projectInfo.isValid()); + + QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished())); + QVERIFY(parserSpy.wait(20000)); + + if (m_isQt4) + expectedNamedQuickTestsCount = expectedUnnamedQuickTestsCount = 0; + + QCOMPARE(m_model->autoTestsCount(), expectedAutoTestsCount); + QCOMPARE(m_model->namedQuickTestsCount(), expectedNamedQuickTestsCount); + QCOMPARE(m_model->unnamedQuickTestsCount(), expectedUnnamedQuickTestsCount); + QCOMPARE(m_model->dataTagsCount(), expectedDataTagsCount); + + QCOMPARE(m_model->parser()->autoTestsCount(), expectedAutoTestsCount); + QCOMPARE(m_model->parser()->namedQuickTestsCount(), expectedNamedQuickTestsCount); + QCOMPARE(m_model->parser()->unnamedQuickTestsCount(), expectedUnnamedQuickTestsCount); + +} + +void AutoTestUnitTests::testCodeParser_data() +{ + QTest::addColumn("projectFilePath"); + QTest::addColumn("expectedAutoTestsCount"); + QTest::addColumn("expectedNamedQuickTestsCount"); + QTest::addColumn("expectedUnnamedQuickTestsCount"); + QTest::addColumn("expectedDataTagsCount"); + + + QTest::newRow("plainAutoTest") + << QString(m_tmpDir->path() + QLatin1String("/plain/plain.pro")) + << 1 << 0 << 0 << 0; + QTest::newRow("mixedAutoTestAndQuickTests") + << QString(m_tmpDir->path() + QLatin1String("/mixed_atp/mixed_atp.pro")) + << 3 << 5 << 3 << 8; + QTest::newRow("plainAutoTestQbs") + << QString(m_tmpDir->path() + QLatin1String("/plain/plain.qbs")) + << 1 << 0 << 0 << 0; + QTest::newRow("mixedAutoTestAndQuickTestsQbs") + << QString(m_tmpDir->path() + QLatin1String("/mixed_atp/mixed_atp.qbs")) + << 3 << 5 << 3 << 8; +} + +void AutoTestUnitTests::testCodeParserSwitchStartup() +{ + QFETCH(QStringList, projectFilePaths); + QFETCH(QList, expectedAutoTestsCount); + QFETCH(QList, expectedNamedQuickTestsCount); + QFETCH(QList, expectedUnnamedQuickTestsCount); + QFETCH(QList, expectedDataTagsCount); + + NavigationWidget *navigation = NavigationWidget::instance(); + navigation->activateSubWidget(Constants::AUTOTEST_ID); + + CppTools::Tests::ProjectOpenerAndCloser projectManager; + for (int i = 0; i < projectFilePaths.size(); ++i) { + qDebug() << "Opening project" << projectFilePaths.at(i); + CppTools::ProjectInfo projectInfo = projectManager.open(projectFilePaths.at(i), true); + QVERIFY(projectInfo.isValid()); + + QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished())); + QVERIFY(parserSpy.wait(20000)); + + QCOMPARE(m_model->autoTestsCount(), expectedAutoTestsCount.at(i)); + QCOMPARE(m_model->namedQuickTestsCount(), + m_isQt4 ? 0 : expectedNamedQuickTestsCount.at(i)); + QCOMPARE(m_model->unnamedQuickTestsCount(), + m_isQt4 ? 0 : expectedUnnamedQuickTestsCount.at(i)); + QCOMPARE(m_model->dataTagsCount(), + expectedDataTagsCount.at(i)); + + QCOMPARE(m_model->parser()->autoTestsCount(), expectedAutoTestsCount.at(i)); + QCOMPARE(m_model->parser()->namedQuickTestsCount(), + m_isQt4 ? 0 : expectedNamedQuickTestsCount.at(i)); + QCOMPARE(m_model->parser()->unnamedQuickTestsCount(), + m_isQt4 ? 0 : expectedUnnamedQuickTestsCount.at(i)); + } +} + +void AutoTestUnitTests::testCodeParserSwitchStartup_data() +{ + QTest::addColumn("projectFilePaths"); + QTest::addColumn >("expectedAutoTestsCount"); + QTest::addColumn >("expectedNamedQuickTestsCount"); + QTest::addColumn >("expectedUnnamedQuickTestsCount"); + QTest::addColumn >("expectedDataTagsCount"); + + QStringList projects = QStringList() + << QString(m_tmpDir->path() + QLatin1String("/plain/plain.pro")) + << QString(m_tmpDir->path() + QLatin1String("/mixed_atp/mixed_atp.pro")) + << QString(m_tmpDir->path() + QLatin1String("/plain/plain.qbs")) + << QString(m_tmpDir->path() + QLatin1String("/mixed_atp/mixed_atp.qbs")); + + QList expectedAutoTests = QList() << 1 << 3 << 1 << 3; + QList expectedNamedQuickTests = QList() << 0 << 5 << 0 << 5; + QList expectedUnnamedQuickTests = QList() << 0 << 3 << 0 << 3; + QList expectedDataTagsCount = QList() << 0 << 8 << 0 << 8; + + QTest::newRow("loadMultipleProjects") + << projects << expectedAutoTests << expectedNamedQuickTests + << expectedUnnamedQuickTests << expectedDataTagsCount; +} + +void AutoTestUnitTests::testCodeParserGTest() +{ + if (qgetenv("GOOGLETEST_DIR").isEmpty()) + QSKIP("This test needs googletest - set GOOGLETEST_DIR (point to googletest repository)"); + + NavigationWidget *navigation = NavigationWidget::instance(); + navigation->activateSubWidget(Constants::AUTOTEST_ID); + + CppTools::Tests::ProjectOpenerAndCloser projectManager; + CppTools::ProjectInfo projectInfo = projectManager.open( + QString(m_tmpDir->path() + QLatin1String("/simple_gt/simple_gt.pro")), true); + QVERIFY(projectInfo.isValid()); + + QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished())); + QVERIFY(parserSpy.wait(20000)); + + QCOMPARE(m_model->gtestNamesCount(), 6); + // 11 == 3 + 2 + 2 + 2 + 1 + 1, see below + QCOMPARE(m_model->parser()->gtestNamesAndSetsCount(), 11); + + QMultiMap expectedNamesAndSets; + expectedNamesAndSets.insert(QStringLiteral("FactorialTest"), 3); + expectedNamesAndSets.insert(QStringLiteral("FactorialTest_Iterative"), 2); + expectedNamesAndSets.insert(QStringLiteral("Sum"), 2); + expectedNamesAndSets.insert(QStringLiteral("QueueTest"), 2); + expectedNamesAndSets.insert(QStringLiteral("DummyTest"), 1); // used as parameterized test + expectedNamesAndSets.insert(QStringLiteral("DummyTest"), 1); // used as 'normal' test + + QMultiMap foundNamesAndSets = m_model->gtestNamesAndSets(); + QCOMPARE(expectedNamesAndSets.size(), foundNamesAndSets.size()); + foreach (const QString &name, expectedNamesAndSets.keys()) + QCOMPARE(expectedNamesAndSets.values(name), foundNamesAndSets.values(name)); + + // check also that no Qt related tests have been found + QCOMPARE(m_model->autoTestsCount(), 0); + QCOMPARE(m_model->namedQuickTestsCount(), 0); + QCOMPARE(m_model->unnamedQuickTestsCount(), 0); + QCOMPARE(m_model->dataTagsCount(), 0); + + QCOMPARE(m_model->parser()->autoTestsCount(), 0); + QCOMPARE(m_model->parser()->namedQuickTestsCount(), 0); + QCOMPARE(m_model->parser()->unnamedQuickTestsCount(), 0); +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/autotestunittests.h b/src/plugins/autotest/autotestunittests.h new file mode 100644 index 00000000000..b5e808d3ce8 --- /dev/null +++ b/src/plugins/autotest/autotestunittests.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef AUTOTESTUNITTESTS_H +#define AUTOTESTUNITTESTS_H + +#include +#include + +namespace CppTools { namespace Tests { class TemporaryCopiedDir; } } + +namespace Autotest { +namespace Internal { + +class TestTreeModel; + +class AutoTestUnitTests : public QObject +{ + Q_OBJECT +public: + explicit AutoTestUnitTests(TestTreeModel *model, QObject *parent = 0); + +signals: + +private slots: + void initTestCase(); + void cleanupTestCase(); + void testCodeParser(); + void testCodeParser_data(); + void testCodeParserSwitchStartup(); + void testCodeParserSwitchStartup_data(); + void testCodeParserGTest(); + +private: + TestTreeModel *m_model; + CppTools::Tests::TemporaryCopiedDir *m_tmpDir; + bool m_isQt4; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // AUTOTESTUNITTESTS_H diff --git a/src/plugins/autotest/autotestunittests.qrc b/src/plugins/autotest/autotestunittests.qrc new file mode 100644 index 00000000000..861f5c26654 --- /dev/null +++ b/src/plugins/autotest/autotestunittests.qrc @@ -0,0 +1,57 @@ + + + unit_test/mixed_atp/src/main.cpp + unit_test/mixed_atp/tests/auto/bench/tst_benchtest.cpp + unit_test/mixed_atp/tests/auto/dummy/tst_foo.cpp + unit_test/mixed_atp/tests/auto/dummy/tst_foo.h + unit_test/mixed_atp/tests/auto/gui/tst_guitest.cpp + unit_test/mixed_atp/tests/auto/quickauto/bar/tst_foo.qml + unit_test/mixed_atp/tests/auto/quickauto/notlisted/tst_bla.qml + unit_test/mixed_atp/tests/auto/quickauto/main.cpp + unit_test/mixed_atp/tests/auto/quickauto/TestDummy.qml + unit_test/mixed_atp/tests/auto/quickauto/tst_test1.qml + unit_test/mixed_atp/tests/auto/quickauto/tst_test2.qml + unit_test/mixed_atp/tests/auto/quickauto/tst_test3.qml + unit_test/mixed_atp/tests/auto/quickauto2/main.cpp + unit_test/mixed_atp/tests/auto/quickauto2/tst_test1.qml + unit_test/mixed_atp/tests/auto/quickauto2/tst_test2.qml + unit_test/plain/test_plain/tst_simple.cpp + unit_test/plain/test_plain/tst_simple.h + unit_test/plain/plain.pro + unit_test/mixed_atp/mixed_atp.pro + unit_test/plain/test_plain/test_plain.pro + unit_test/mixed_atp/tests/tests.pro + unit_test/mixed_atp/src/src.pro + unit_test/mixed_atp/tests/auto/bench/bench.pro + unit_test/mixed_atp/tests/auto/dummy/dummy.pro + unit_test/mixed_atp/tests/auto/gui/gui.pro + unit_test/mixed_atp/tests/auto/quickauto/quickauto.pro + unit_test/mixed_atp/tests/auto/quickauto2/quickauto2.pro + unit_test/mixed_atp/tests/auto/auto.pro + unit_test/plain/plain.qbs + unit_test/plain/test_plain/test_plain.qbs + unit_test/mixed_atp/mixed_atp.qbs + unit_test/mixed_atp/src/src.qbs + unit_test/mixed_atp/tests/tests.qbs + unit_test/mixed_atp/tests/auto/auto.qbs + unit_test/mixed_atp/tests/auto/bench/bench.qbs + unit_test/mixed_atp/tests/auto/dummy/dummy.qbs + unit_test/mixed_atp/tests/auto/gui/gui.qbs + unit_test/mixed_atp/tests/auto/quickauto/quickauto.qbs + unit_test/mixed_atp/tests/auto/quickauto2/quickauto2.qbs + unit_test/simple_gt/src/main.cpp + unit_test/simple_gt/src/src.pro + unit_test/simple_gt/tests/gt1/further.cpp + unit_test/simple_gt/tests/gt1/gt1.pro + unit_test/simple_gt/tests/gt1/main.cpp + unit_test/simple_gt/tests/gt2/gt2.pro + unit_test/simple_gt/tests/gt2/main.cpp + unit_test/simple_gt/tests/gt2/queuetest.h + unit_test/simple_gt/tests/tests.pro + unit_test/simple_gt/simple_gt.pro + unit_test/simple_gt/tests/gtest_dependency.pri + unit_test/simple_gt/tests/gt3/dummytest.h + unit_test/simple_gt/tests/gt3/gt3.pro + unit_test/simple_gt/tests/gt3/main.cpp + + diff --git a/src/plugins/autotest/images/autotest.png b/src/plugins/autotest/images/autotest.png new file mode 100644 index 00000000000..3f52e9bf08f Binary files /dev/null and b/src/plugins/autotest/images/autotest.png differ diff --git a/src/plugins/autotest/images/benchmark.png b/src/plugins/autotest/images/benchmark.png new file mode 100644 index 00000000000..c9d3c5b2b50 Binary files /dev/null and b/src/plugins/autotest/images/benchmark.png differ diff --git a/src/plugins/autotest/images/blacklisted_fail.png b/src/plugins/autotest/images/blacklisted_fail.png new file mode 100644 index 00000000000..695e1f92468 Binary files /dev/null and b/src/plugins/autotest/images/blacklisted_fail.png differ diff --git a/src/plugins/autotest/images/blacklisted_pass.png b/src/plugins/autotest/images/blacklisted_pass.png new file mode 100644 index 00000000000..bac3ffb1342 Binary files /dev/null and b/src/plugins/autotest/images/blacklisted_pass.png differ diff --git a/src/plugins/autotest/images/class.png b/src/plugins/autotest/images/class.png new file mode 100644 index 00000000000..88432d2cb10 Binary files /dev/null and b/src/plugins/autotest/images/class.png differ diff --git a/src/plugins/autotest/images/collapse.png b/src/plugins/autotest/images/collapse.png new file mode 100644 index 00000000000..c8dc840a61e Binary files /dev/null and b/src/plugins/autotest/images/collapse.png differ diff --git a/src/plugins/autotest/images/collapse@2x.png b/src/plugins/autotest/images/collapse@2x.png new file mode 100644 index 00000000000..823e6cc6496 Binary files /dev/null and b/src/plugins/autotest/images/collapse@2x.png differ diff --git a/src/plugins/autotest/images/data.png b/src/plugins/autotest/images/data.png new file mode 100644 index 00000000000..1f510c40424 Binary files /dev/null and b/src/plugins/autotest/images/data.png differ diff --git a/src/plugins/autotest/images/debug.png b/src/plugins/autotest/images/debug.png new file mode 100644 index 00000000000..e18bd157e4a Binary files /dev/null and b/src/plugins/autotest/images/debug.png differ diff --git a/src/plugins/autotest/images/expand.png b/src/plugins/autotest/images/expand.png new file mode 100644 index 00000000000..a8b3f4a486e Binary files /dev/null and b/src/plugins/autotest/images/expand.png differ diff --git a/src/plugins/autotest/images/expand@2x.png b/src/plugins/autotest/images/expand@2x.png new file mode 100644 index 00000000000..c3466cd2ab6 Binary files /dev/null and b/src/plugins/autotest/images/expand@2x.png differ diff --git a/src/plugins/autotest/images/fail.png b/src/plugins/autotest/images/fail.png new file mode 100644 index 00000000000..38c0d1463a9 Binary files /dev/null and b/src/plugins/autotest/images/fail.png differ diff --git a/src/plugins/autotest/images/fatal.png b/src/plugins/autotest/images/fatal.png new file mode 100644 index 00000000000..4e0bf77474a Binary files /dev/null and b/src/plugins/autotest/images/fatal.png differ diff --git a/src/plugins/autotest/images/func.png b/src/plugins/autotest/images/func.png new file mode 100644 index 00000000000..8f585e875d1 Binary files /dev/null and b/src/plugins/autotest/images/func.png differ diff --git a/src/plugins/autotest/images/leafsort.png b/src/plugins/autotest/images/leafsort.png new file mode 100644 index 00000000000..c9f744bde7c Binary files /dev/null and b/src/plugins/autotest/images/leafsort.png differ diff --git a/src/plugins/autotest/images/leafsort@2x.png b/src/plugins/autotest/images/leafsort@2x.png new file mode 100644 index 00000000000..b74a94fc167 Binary files /dev/null and b/src/plugins/autotest/images/leafsort@2x.png differ diff --git a/src/plugins/autotest/images/pass.png b/src/plugins/autotest/images/pass.png new file mode 100644 index 00000000000..33dbe44649e Binary files /dev/null and b/src/plugins/autotest/images/pass.png differ diff --git a/src/plugins/autotest/images/runselected_boxes.png b/src/plugins/autotest/images/runselected_boxes.png new file mode 100644 index 00000000000..6e39f2ee0b6 Binary files /dev/null and b/src/plugins/autotest/images/runselected_boxes.png differ diff --git a/src/plugins/autotest/images/runselected_boxes@2x.png b/src/plugins/autotest/images/runselected_boxes@2x.png new file mode 100644 index 00000000000..55e5863a52f Binary files /dev/null and b/src/plugins/autotest/images/runselected_boxes@2x.png differ diff --git a/src/plugins/autotest/images/runselected_tickmarks.png b/src/plugins/autotest/images/runselected_tickmarks.png new file mode 100644 index 00000000000..6296f8748da Binary files /dev/null and b/src/plugins/autotest/images/runselected_tickmarks.png differ diff --git a/src/plugins/autotest/images/runselected_tickmarks@2x.png b/src/plugins/autotest/images/runselected_tickmarks@2x.png new file mode 100644 index 00000000000..75252f173c2 Binary files /dev/null and b/src/plugins/autotest/images/runselected_tickmarks@2x.png differ diff --git a/src/plugins/autotest/images/skip.png b/src/plugins/autotest/images/skip.png new file mode 100644 index 00000000000..9dfea415f16 Binary files /dev/null and b/src/plugins/autotest/images/skip.png differ diff --git a/src/plugins/autotest/images/sort.png b/src/plugins/autotest/images/sort.png new file mode 100644 index 00000000000..c15eb56d50a Binary files /dev/null and b/src/plugins/autotest/images/sort.png differ diff --git a/src/plugins/autotest/images/sort@2x.png b/src/plugins/autotest/images/sort@2x.png new file mode 100644 index 00000000000..1a2e5d95204 Binary files /dev/null and b/src/plugins/autotest/images/sort@2x.png differ diff --git a/src/plugins/autotest/images/warn.png b/src/plugins/autotest/images/warn.png new file mode 100644 index 00000000000..a813c1dc9a6 Binary files /dev/null and b/src/plugins/autotest/images/warn.png differ diff --git a/src/plugins/autotest/images/xfail.png b/src/plugins/autotest/images/xfail.png new file mode 100644 index 00000000000..deeafe7d941 Binary files /dev/null and b/src/plugins/autotest/images/xfail.png differ diff --git a/src/plugins/autotest/images/xpass.png b/src/plugins/autotest/images/xpass.png new file mode 100644 index 00000000000..964bb4a181e Binary files /dev/null and b/src/plugins/autotest/images/xpass.png differ diff --git a/src/plugins/autotest/testcodeparser.cpp b/src/plugins/autotest/testcodeparser.cpp new file mode 100644 index 00000000000..2ac2a1cb874 --- /dev/null +++ b/src/plugins/autotest/testcodeparser.cpp @@ -0,0 +1,1099 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "autotestconstants.h" +#include "autotest_utils.h" +#include "testcodeparser.h" +#include "testinfo.h" +#include "testvisitor.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +static Q_LOGGING_CATEGORY(LOG, "qtc.autotest.testcodeparser") + +namespace Autotest { +namespace Internal { + +TestCodeParser::TestCodeParser(TestTreeModel *parent) + : QObject(parent), + m_model(parent), + m_codeModelParsing(false), + m_fullUpdatePostponed(false), + m_partialUpdatePostponed(false), + m_dirty(false), + m_singleShotScheduled(false), + m_parserState(Disabled) +{ + // connect to ProgressManager to postpone test parsing when CppModelManager is parsing + auto progressManager = qobject_cast(Core::ProgressManager::instance()); + connect(progressManager, &Core::ProgressManager::taskStarted, + this, &TestCodeParser::onTaskStarted); + connect(progressManager, &Core::ProgressManager::allTasksFinished, + this, &TestCodeParser::onAllTasksFinished); + connect(this, &TestCodeParser::partialParsingFinished, + this, &TestCodeParser::onPartialParsingFinished); +} + +TestCodeParser::~TestCodeParser() +{ + clearCache(); +} + +void TestCodeParser::setState(State state) +{ + qCDebug(LOG) << "setState(" << state << "), currentState:" << m_parserState; + // avoid triggering parse before code model parsing has finished, but mark as dirty + if (m_codeModelParsing) { + m_dirty = true; + qCDebug(LOG) << "Not setting new state - code model parsing is running, just marking dirty"; + return; + } + + if ((state == Disabled || state == Idle) + && (m_parserState == PartialParse || m_parserState == FullParse)) { + qCDebug(LOG) << "Not setting state, parse is running"; + return; + } + m_parserState = state; + + if (m_parserState == Disabled) { + m_fullUpdatePostponed = m_partialUpdatePostponed = false; + m_postponedFiles.clear(); + } else if (m_parserState == Idle && ProjectExplorer::SessionManager::startupProject()) { + if (m_fullUpdatePostponed || m_dirty) { + emitUpdateTestTree(); + } else if (m_partialUpdatePostponed) { + m_partialUpdatePostponed = false; + qCDebug(LOG) << "calling scanForTests with postponed files (setState)"; + scanForTests(m_postponedFiles.toList()); + } + } +} + +void TestCodeParser::emitUpdateTestTree() +{ + if (m_singleShotScheduled) { + qCDebug(LOG) << "not scheduling another updateTestTree"; + return; + } + + qCDebug(LOG) << "adding singleShot"; + m_singleShotScheduled = true; + QTimer::singleShot(1000, this, SLOT(updateTestTree())); +} + +void TestCodeParser::updateTestTree() +{ + m_singleShotScheduled = false; + if (m_codeModelParsing) { + m_fullUpdatePostponed = true; + m_partialUpdatePostponed = false; + m_postponedFiles.clear(); + return; + } + + if (!ProjectExplorer::SessionManager::startupProject()) + return; + + m_fullUpdatePostponed = false; + + clearCache(); + qCDebug(LOG) << "calling scanForTests (updateTestTree)"; + scanForTests(); +} + +/****** scan for QTest related stuff helpers ******/ + +static QByteArray getFileContent(QString filePath) +{ + QByteArray fileContent; + CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + CppTools::WorkingCopy wc = cppMM->workingCopy(); + if (wc.contains(filePath)) { + fileContent = wc.source(filePath); + } else { + QString error; + const QTextCodec *codec = Core::EditorManager::defaultTextCodec(); + if (Utils::TextFileFormat::readFileUTF8(filePath, codec, &fileContent, &error) + != Utils::TextFileFormat::ReadSuccess) { + qDebug() << "Failed to read file" << filePath << ":" << error; + } + } + return fileContent; +} + +static bool includesQtTest(const CPlusPlus::Document::Ptr &doc, + const CppTools::CppModelManager *cppMM) +{ + static QString expectedHeaderPrefix + = Utils::HostOsInfo::isMacHost() + ? QLatin1String("QtTest.framework/Headers") + : QLatin1String("QtTest"); + + const QList includes = doc->resolvedIncludes(); + + foreach (const CPlusPlus::Document::Include &inc, includes) { + // TODO this short cut works only for #include + // bad, as there could be much more different approaches + if (inc.unresolvedFileName() == QLatin1String("QtTest") + && inc.resolvedFileName().endsWith( + QString::fromLatin1("%1/QtTest").arg(expectedHeaderPrefix))) { + return true; + } + } + + if (cppMM) { + CPlusPlus::Snapshot snapshot = cppMM->snapshot(); + const QSet allIncludes = snapshot.allIncludesForDocument(doc->fileName()); + foreach (const QString &include, allIncludes) { + + if (include.endsWith(QString::fromLatin1("%1/qtest.h").arg(expectedHeaderPrefix))) { + return true; + } + } + } + return false; +} + +static bool includesQtQuickTest(const CPlusPlus::Document::Ptr &doc, + const CppTools::CppModelManager *cppMM) +{ + static QString expectedHeaderPrefix + = Utils::HostOsInfo::isMacHost() + ? QLatin1String("QtQuickTest.framework/Headers") + : QLatin1String("QtQuickTest"); + + const QList includes = doc->resolvedIncludes(); + + foreach (const CPlusPlus::Document::Include &inc, includes) { + if (inc.unresolvedFileName() == QLatin1String("QtQuickTest/quicktest.h") + && inc.resolvedFileName().endsWith( + QString::fromLatin1("%1/quicktest.h").arg(expectedHeaderPrefix))) { + return true; + } + } + + if (cppMM) { + foreach (const QString &include, cppMM->snapshot().allIncludesForDocument(doc->fileName())) { + if (include.endsWith(QString::fromLatin1("%1/quicktest.h").arg(expectedHeaderPrefix))) + return true; + } + } + return false; +} + +static bool includesGTest(const CPlusPlus::Document::Ptr &doc, + const CppTools::CppModelManager *cppMM) +{ + const QString gtestH = QLatin1String("gtest/gtest.h"); + foreach (const CPlusPlus::Document::Include &inc, doc->resolvedIncludes()) { + if (inc.resolvedFileName().endsWith(gtestH)) + return true; + } + + if (cppMM) { + const CPlusPlus::Snapshot snapshot = cppMM->snapshot(); + foreach (const QString &include, snapshot.allIncludesForDocument(doc->fileName())) { + if (include.endsWith(gtestH)) + return true; + } + } + + return false; +} + +static bool qtTestLibDefined(const CppTools::CppModelManager *cppMM, + const QString &fileName) +{ + const QList parts = cppMM->projectPart(fileName); + if (parts.size() > 0) + return parts.at(0)->projectDefines.contains("#define QT_TESTLIB_LIB 1"); + return false; +} + +static QString quickTestSrcDir(const CppTools::CppModelManager *cppMM, + const QString &fileName) +{ + static const QByteArray qtsd(" QUICK_TEST_SOURCE_DIR "); + const QList parts = cppMM->projectPart(fileName); + if (parts.size() > 0) { + QByteArray projDefines(parts.at(0)->projectDefines); + foreach (const QByteArray &line, projDefines.split('\n')) { + if (line.contains(qtsd)) { + QByteArray result = line.mid(line.indexOf(qtsd) + qtsd.length()); + if (result.startsWith('"')) + result.remove(result.length() - 1, 1).remove(0, 1); + if (result.startsWith("\\\"")) + result.remove(result.length() - 2, 2).remove(0, 2); + return QLatin1String(result); + } + } + } + return QString(); +} + +static QString testClass(const CppTools::CppModelManager *modelManager, + CPlusPlus::Document::Ptr &document) +{ + const QList macros = document->macroUses(); + + foreach (const CPlusPlus::Document::MacroUse ¯o, macros) { + if (!macro.isFunctionLike()) + continue; + const QByteArray name = macro.macro().name(); + if (TestUtils::isQTestMacro(name)) { + const CPlusPlus::Document::Block arg = macro.arguments().at(0); + return QLatin1String(getFileContent(document->fileName()) + .mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin())); + } + } + // check if one has used a self-defined macro or QTest::qExec() directly + const CPlusPlus::Snapshot snapshot = modelManager->snapshot(); + const QByteArray fileContent = getFileContent(document->fileName()); + document = snapshot.preprocessedDocument(fileContent, document->fileName()); + document->check(); + CPlusPlus::AST *ast = document->translationUnit()->ast(); + TestAstVisitor astVisitor(document); + astVisitor.accept(ast); + return astVisitor.className(); +} + +static QString quickTestName(const CPlusPlus::Document::Ptr &doc) +{ + const QList macros = doc->macroUses(); + + foreach (const CPlusPlus::Document::MacroUse ¯o, macros) { + if (!macro.isFunctionLike()) + continue; + const QByteArray name = macro.macro().name(); + if (TestUtils::isQuickTestMacro(name)) { + CPlusPlus::Document::Block arg = macro.arguments().at(0); + return QLatin1String(getFileContent(doc->fileName()) + .mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin())); + } + } + return QString(); +} + +static bool hasGTestNames(CPlusPlus::Document::Ptr &document) +{ + foreach (const CPlusPlus::Document::MacroUse ¯o, document->macroUses()) { + if (!macro.isFunctionLike()) + continue; + if (TestUtils::isGTestMacro(QLatin1String(macro.macro().name()))) { + const QVector args = macro.arguments(); + if (args.size() != 2) + continue; + return true; + } + } + return false; +} + +static QList scanDirectoryForQuickTestQmlFiles(const QString &srcDir) +{ + QStringList dirs(srcDir); + QmlJS::ModelManagerInterface *qmlJsMM = QmlJSTools::Internal::ModelManager::instance(); + // make sure even files not listed in pro file are available inside the snapshot + QFutureInterface future; + QmlJS::PathsAndLanguages paths; + paths.maybeInsert(Utils::FileName::fromString(srcDir), QmlJS::Dialect::Qml); + const bool emitDocumentChanges = false; + const bool onlyTheLib = false; + QmlJS::ModelManagerInterface::importScan(future, qmlJsMM->workingCopy(), paths, qmlJsMM, + emitDocumentChanges, onlyTheLib); + + const QmlJS::Snapshot snapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot(); + QDirIterator it(srcDir, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + QFileInfo fi(it.fileInfo().canonicalFilePath()); + dirs << fi.filePath(); + } + QList foundDocs; + + foreach (const QString &path, dirs) { + const QList docs = snapshot.documentsInDirectory(path); + foreach (const QmlJS::Document::Ptr &doc, docs) { + const QString fileName(QFileInfo(doc->fileName()).fileName()); + if (fileName.startsWith(QLatin1String("tst_")) && fileName.endsWith(QLatin1String(".qml"))) + foundDocs << doc; + } + } + + return foundDocs; +} + +static CPlusPlus::Document::Ptr declaringDocument(CPlusPlus::Document::Ptr doc, + const QString &testCaseName, + unsigned *line, unsigned *column) +{ + CPlusPlus::Document::Ptr declaringDoc = doc; + const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + CPlusPlus::TypeOfExpression typeOfExpr; + typeOfExpr.init(doc, cppMM->snapshot()); + + auto lookupItems = typeOfExpr(testCaseName.toUtf8(), doc->globalNamespace()); + if (lookupItems.size()) { + CPlusPlus::Class *toeClass = lookupItems.first().declaration()->asClass(); + if (toeClass) { + const QString declFileName = QLatin1String(toeClass->fileId()->chars(), + toeClass->fileId()->size()); + declaringDoc = cppMM->snapshot().document(declFileName); + *line = toeClass->line(); + *column = toeClass->column() - 1; + } + } + return declaringDoc; +} + +static bool hasFunctionWithDataTagUsage(const QMap &testFunctions) +{ + foreach (const QString &functionName, testFunctions.keys()) { + if (functionName.endsWith(QLatin1String("_data")) && + testFunctions.contains(functionName.left(functionName.size() - 5))) { + return true; + } + } + return false; +} + +static QMap checkForDataTags(const QString &fileName, + const QMap &testFunctions) +{ + if (hasFunctionWithDataTagUsage(testFunctions)) { + const CPlusPlus::Snapshot snapshot = CPlusPlus::CppModelManagerBase::instance()->snapshot(); + const QByteArray fileContent = getFileContent(fileName); + CPlusPlus::Document::Ptr document = snapshot.preprocessedDocument(fileContent, fileName); + document->check(); + CPlusPlus::AST *ast = document->translationUnit()->ast(); + TestDataFunctionVisitor visitor(document); + visitor.accept(ast); + return visitor.dataTags(); + } + return QMap(); +} + +static TestTreeItem *constructTestTreeItem(const QString &fileName, + const QString &mainFile, // used for Quick Tests only + const QString &testCaseName, + int line, int column, + const QMap &functions, + const QMap dataTags = QMap()) +{ + TestTreeItem *treeItem = new TestTreeItem(testCaseName, fileName, TestTreeItem::TestClass); + treeItem->setMainFile(mainFile); // used for Quick Tests only + treeItem->setLine(line); + treeItem->setColumn(column); + + foreach (const QString &functionName, functions.keys()) { + const TestCodeLocationAndType locationAndType = functions.value(functionName); + TestTreeItem *treeItemChild = new TestTreeItem(functionName, locationAndType.m_name, + locationAndType.m_type); + treeItemChild->setLine(locationAndType.m_line); + treeItemChild->setColumn(locationAndType.m_column); + treeItemChild->setState(locationAndType.m_state); + + // check for data tags and if there are any for this function add them + const QString qualifiedFunctionName = testCaseName + QLatin1String("::") + functionName; + if (dataTags.contains(qualifiedFunctionName)) { + const TestCodeLocationList &tags = dataTags.value(qualifiedFunctionName); + foreach (const TestCodeLocationAndType &tagLocation, tags) { + TestTreeItem *tagTreeItem = new TestTreeItem(tagLocation.m_name, + locationAndType.m_name, + tagLocation.m_type); + tagTreeItem->setLine(tagLocation.m_line); + tagTreeItem->setColumn(tagLocation.m_column); + tagTreeItem->setState(tagLocation.m_state); + treeItemChild->appendChild(tagTreeItem); + } + } + + treeItem->appendChild(treeItemChild); + } + return treeItem; +} + +static TestTreeItem *constructGTestTreeItem(const QString &filePath, const GTestCaseSpec &caseSpec, + const QString &proFile, + const TestCodeLocationList &testSets) +{ + TestTreeItem *item = new TestTreeItem(caseSpec.testCaseName, QString(), + caseSpec.parameterized ? TestTreeItem::GTestCaseParameterized + : TestTreeItem::GTestCase); + foreach (const TestCodeLocationAndType &locationAndType, testSets) { + TestTreeItem *treeItemChild = new TestTreeItem(locationAndType.m_name, filePath, + locationAndType.m_type); + treeItemChild->setState(locationAndType.m_state); + treeItemChild->setLine(locationAndType.m_line); + treeItemChild->setColumn(locationAndType.m_column); + treeItemChild->setMainFile(proFile); + item->appendChild(treeItemChild); + } + return item; +} + +/****** end of helpers ******/ + +// used internally to indicate a parse that failed due to having triggered a parse for a file that +// is not (yet) part of the CppModelManager's snapshot +static bool parsingHasFailed; + +void performParse(QFutureInterface &futureInterface, QStringList list, + TestCodeParser *testCodeParser) +{ + int progressValue = 0; + futureInterface.setProgressRange(0, list.size()); + futureInterface.setProgressValue(progressValue); + CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + CPlusPlus::Snapshot snapshot = cppMM->snapshot(); + + foreach (const QString &file, list) { + if (snapshot.contains(file)) { + CPlusPlus::Document::Ptr doc = snapshot.find(file).value(); + futureInterface.setProgressValue(++progressValue); + testCodeParser->checkDocumentForTestCode(doc); + } else { + parsingHasFailed |= (CppTools::ProjectFile::classify(file) + != CppTools::ProjectFile::Unclassified); + } + } + futureInterface.setProgressValue(list.size()); +} + +/****** threaded parsing stuff *******/ +void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document) +{ + const QString fileName = document->fileName(); + const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); + + QList projParts = modelManager->projectPart(fileName); + if (projParts.size()) + if (!projParts.at(0)->selectedForBuilding) { + removeTestsIfNecessary(fileName); + return; + } + + if (includesQtQuickTest(document, modelManager)) { + handleQtQuickTest(document); + return; + } + + if (includesQtTest(document, modelManager) && qtTestLibDefined(modelManager, fileName)) { + QString testCaseName(testClass(modelManager, document)); + if (!testCaseName.isEmpty()) { + unsigned line = 0; + unsigned column = 0; + CPlusPlus::Document::Ptr declaringDoc = declaringDocument(document, testCaseName, + &line, &column); + if (declaringDoc.isNull()) + return; + + TestVisitor visitor(testCaseName); + visitor.accept(declaringDoc->globalNamespace()); + const QMap testFunctions = visitor.privateSlots(); + + QMap dataTags = + checkForDataTags(declaringDoc->fileName(), testFunctions); + + if (declaringDoc->fileName() != document->fileName()) + dataTags.unite(checkForDataTags(document->fileName(), testFunctions)); + + TestTreeItem *item = constructTestTreeItem(declaringDoc->fileName(), QString(), + testCaseName, line, column, testFunctions, + dataTags); + + updateModelAndCppDocMap(document, declaringDoc->fileName(), item); + return; + } + } + + if (includesGTest(document, modelManager)) { + if (hasGTestNames(document)) { + handleGTest(document->fileName()); + return; + } + } + + // could not find the class to test, or QTest is not included and QT_TESTLIB_LIB defined + // maybe file is only a referenced file + if (m_cppDocMap.contains(fileName)) { + const TestInfo info = m_cppDocMap[fileName]; + CPlusPlus::Snapshot snapshot = modelManager->snapshot(); + if (snapshot.contains(info.referencingFile())) { + checkDocumentForTestCode(snapshot.find(info.referencingFile()).value()); + } else { // no referencing file too, so this test case is no more a test case + m_cppDocMap.remove(fileName); + emit testItemsRemoved(fileName, TestTreeModel::AutoTest); + } + } +} + +void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr document) +{ + const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance(); + + if (quickTestName(document).isEmpty()) + return; + + const QString cppFileName = document->fileName(); + const QString srcDir = quickTestSrcDir(modelManager, cppFileName); + if (srcDir.isEmpty()) + return; + + const QList qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir); + foreach (const QmlJS::Document::Ptr &qmlJSDoc, qmlDocs) { + QmlJS::AST::Node *ast = qmlJSDoc->ast(); + QTC_ASSERT(ast, continue); + TestQmlVisitor qmlVisitor(qmlJSDoc); + QmlJS::AST::Node::accept(ast, &qmlVisitor); + + const QString testCaseName = qmlVisitor.testCaseName(); + const TestCodeLocationAndType tcLocationAndType = qmlVisitor.testCaseLocation(); + const QMap testFunctions = qmlVisitor.testFunctions(); + + if (testCaseName.isEmpty()) { + updateUnnamedQuickTests(qmlJSDoc->fileName(), cppFileName, testFunctions); + continue; + } // end of handling test cases without name property + + // construct new/modified TestTreeItem + TestTreeItem *testTreeItem + = constructTestTreeItem(tcLocationAndType.m_name, cppFileName, testCaseName, + tcLocationAndType.m_line, tcLocationAndType.m_column, + testFunctions); + + // update model and internal map + updateModelAndQuickDocMap(qmlJSDoc, cppFileName, testTreeItem); + } +} + +void TestCodeParser::handleGTest(const QString &filePath) +{ + const QByteArray &fileContent = getFileContent(filePath); + const CPlusPlus::Snapshot snapshot = CPlusPlus::CppModelManagerBase::instance()->snapshot(); + CPlusPlus::Document::Ptr document = snapshot.preprocessedDocument(fileContent, filePath); + document->check(); + CPlusPlus::AST *ast = document->translationUnit()->ast(); + GTestVisitor visitor(document); + visitor.accept(ast); + + QMap result = visitor.gtestFunctions(); + updateGTests(document, result); +} + +void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document) +{ + if (m_codeModelParsing) { + if (!m_fullUpdatePostponed) { + m_partialUpdatePostponed = true; + m_postponedFiles.insert(document->fileName()); + } + return; + } + + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (!project) + return; + const QString fileName = document->fileName(); + if (m_cppDocMap.contains(fileName)) { + if (m_cppDocMap[fileName].revision() == document->revision() + && m_cppDocMap[fileName].editorRevision() == document->editorRevision()) { + return; + } + } else if (!project->files(ProjectExplorer::Project::AllFiles).contains(fileName)) { + return; + } + qCDebug(LOG) << "calling scanForTests (onCppDocumentUpdated)"; + scanForTests(QStringList(fileName)); +} + +void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document) +{ + if (m_codeModelParsing) { + if (!m_fullUpdatePostponed) { + m_partialUpdatePostponed = true; + m_postponedFiles.insert(document->fileName()); + } + return; + } + + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (!project) + return; + const QString fileName = document->fileName(); + if (m_quickDocMap.contains(fileName)) { + if ((int)m_quickDocMap[fileName].editorRevision() == document->editorRevision()) { + return; + } + } else if (!project->files(ProjectExplorer::Project::AllFiles).contains(fileName)) { + // what if the file is not listed inside the pro file, but will be used anyway? + return; + } + const CPlusPlus::Snapshot snapshot = CppTools::CppModelManager::instance()->snapshot(); + if (m_quickDocMap.contains(fileName) + && snapshot.contains(m_quickDocMap[fileName].referencingFile())) { + if (!m_quickDocMap[fileName].referencingFile().isEmpty()) { + qCDebug(LOG) << "calling scanForTests with cached referencing files" + << "(onQmlDocumentUpdated)"; + scanForTests(QStringList(m_quickDocMap[fileName].referencingFile())); + } + } + if (m_unnamedQuickDocList.size() == 0) + return; + + // special case of having unnamed TestCases + const QString &mainFile = m_model->getMainFileForUnnamedQuickTest(fileName); + if (!mainFile.isEmpty() && snapshot.contains(mainFile)) { + qCDebug(LOG) << "calling scanForTests with mainfile (onQmlDocumentUpdated)"; + scanForTests(QStringList(mainFile)); + } +} + +void TestCodeParser::onStartupProjectChanged(ProjectExplorer::Project *) +{ + if (m_parserState == FullParse || m_parserState == PartialParse) { + Core::ProgressManager::instance()->cancelTasks(Constants::TASK_PARSE); + } else { + clearCache(); + emitUpdateTestTree(); + } +} + +void TestCodeParser::onProjectPartsUpdated(ProjectExplorer::Project *project) +{ + if (project != ProjectExplorer::SessionManager::startupProject()) + return; + if (m_codeModelParsing || m_parserState == Disabled) + m_fullUpdatePostponed = true; + else + emitUpdateTestTree(); +} + +void TestCodeParser::removeFiles(const QStringList &files) +{ + foreach (const QString &file, files) + removeTestsIfNecessary(file); +} + +bool TestCodeParser::postponed(const QStringList &fileList) +{ + switch (m_parserState) { + case Idle: + return false; + case PartialParse: + case FullParse: + // parse is running, postponing a full parse + if (fileList.isEmpty()) { + m_partialUpdatePostponed = false; + m_postponedFiles.clear(); + m_fullUpdatePostponed = true; + } else { + // partial parse triggered, but full parse is postponed already, ignoring this + if (m_fullUpdatePostponed) + return true; + // partial parse triggered, postpone or add current files to already postponed partial + foreach (const QString &file, fileList) + m_postponedFiles.insert(file); + m_partialUpdatePostponed = true; + } + return true; + case Disabled: + break; + } + QTC_ASSERT(false, return false); // should not happen at all +} + +void TestCodeParser::scanForTests(const QStringList &fileList) +{ + if (m_parserState == Disabled) { + m_dirty = true; + if (fileList.isEmpty()) { + m_fullUpdatePostponed = true; + m_partialUpdatePostponed = false; + m_postponedFiles.clear(); + } else { + if (!m_fullUpdatePostponed) { + m_partialUpdatePostponed = true; + foreach (const QString &file, fileList) + m_postponedFiles.insert(file); + } + } + return; + } + + if (postponed(fileList)) + return; + + m_postponedFiles.clear(); + bool isFullParse = fileList.isEmpty(); + bool isSmallChange = !isFullParse && fileList.size() < 6; + QStringList list; + if (isFullParse) { + list = ProjectExplorer::SessionManager::startupProject()->files(ProjectExplorer::Project::AllFiles); + if (list.isEmpty()) + return; + qCDebug(LOG) << "setting state to FullParse (scanForTests)"; + m_parserState = FullParse; + } else { + list << fileList; + qCDebug(LOG) << "setting state to PartialParse (scanForTests)"; + m_parserState = PartialParse; + } + + parsingHasFailed = false; + if (isSmallChange) { // no need to do this async or should we do this always async? + CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + CPlusPlus::Snapshot snapshot = cppMM->snapshot(); + foreach (const QString &file, list) { + if (snapshot.contains(file)) { + CPlusPlus::Document::Ptr doc = snapshot.find(file).value(); + checkDocumentForTestCode(doc); + } else { + parsingHasFailed |= (CppTools::ProjectFile::classify(file) + != CppTools::ProjectFile::Unclassified); + } + } + onFinished(); + return; + } + + QFuture future = Utils::runAsync(&performParse, list, this); + Core::FutureProgress *progress + = Core::ProgressManager::addTask(future, isFullParse ? tr("Scanning for Tests") + : tr("Refreshing Tests List"), + Autotest::Constants::TASK_PARSE); + connect(progress, &Core::FutureProgress::finished, + this, &TestCodeParser::onFinished); + + emit parsingStarted(); +} + +void TestCodeParser::clearCache() +{ + m_cppDocMap.clear(); + m_quickDocMap.clear(); + m_unnamedQuickDocList.clear(); + m_gtestDocMap.clear(); + m_gtestDocList.clear(); + emit cacheCleared(); +} + +void TestCodeParser::removeTestsIfNecessary(const QString &fileName) +{ + // check if this file was listed before and remove if necessary (switched config,...) + if (m_cppDocMap.contains(fileName)) { + m_cppDocMap.remove(fileName); + emit testItemsRemoved(fileName, TestTreeModel::AutoTest); + } else if (m_gtestDocMap.contains(fileName)) { + m_gtestDocMap.remove(fileName); + emit testItemsRemoved(fileName, TestTreeModel::GoogleTest); + } else { // handle Qt Quick Tests + QList toBeRemoved; + foreach (const QString &file, m_quickDocMap.keys()) { + if (file == fileName) { + toBeRemoved.append(file); + continue; + } + const TestInfo info = m_quickDocMap.value(file); + if (info.referencingFile() == fileName) + toBeRemoved.append(file); + } + foreach (const QString &file, toBeRemoved) { + m_quickDocMap.remove(file); + emit testItemsRemoved(file, TestTreeModel::QuickTest); + } + // unnamed Quick Tests must be handled separately + if (fileName.endsWith(QLatin1String(".qml"))) { + removeUnnamedQuickTestsByName(fileName); + } else { + QSet filePaths; + m_model->qmlFilesForMainFile(fileName, &filePaths); + foreach (const QString &file, filePaths) + removeUnnamedQuickTestsByName(file); + } + } +} + +void TestCodeParser::onTaskStarted(Core::Id type) +{ + if (type == CppTools::Constants::TASK_INDEX) + m_codeModelParsing = true; +} + +void TestCodeParser::onAllTasksFinished(Core::Id type) +{ + // only CPP parsing is relevant as we trigger Qml parsing internally anyway + if (type != CppTools::Constants::TASK_INDEX) + return; + m_codeModelParsing = false; + + // avoid illegal parser state if respective widgets became hidden while parsing + setState(Idle); +} + +void TestCodeParser::onFinished() +{ + switch (m_parserState) { + case PartialParse: + qCDebug(LOG) << "setting state to Idle (onFinished, PartialParse)"; + m_parserState = Idle; + emit partialParsingFinished(); + break; + case FullParse: + qCDebug(LOG) << "setting state to Idle (onFinished, FullParse)"; + m_parserState = Idle; + m_dirty = parsingHasFailed; + if (m_partialUpdatePostponed || m_fullUpdatePostponed || parsingHasFailed) { + emit partialParsingFinished(); + } else { + qCDebug(LOG) << "emitting parsingFinished" + << "(onFinished, FullParse, nothing postponed, parsing succeeded)"; + emit parsingFinished(); + } + m_dirty = false; + break; + case Disabled: // can happen if all Test related widgets become hidden while parsing + qCDebug(LOG) << "emitting parsingFinished (onFinished, Disabled)"; + emit parsingFinished(); + break; + default: + qWarning("I should not be here... State: %d", m_parserState); + break; + } +} + +void TestCodeParser::onPartialParsingFinished() +{ + QTC_ASSERT(m_fullUpdatePostponed != m_partialUpdatePostponed + || ((m_fullUpdatePostponed || m_partialUpdatePostponed) == false), + m_partialUpdatePostponed = false;m_postponedFiles.clear();); + if (m_fullUpdatePostponed) { + m_fullUpdatePostponed = false; + qCDebug(LOG) << "calling updateTestTree (onPartialParsingFinished)"; + updateTestTree(); + } else if (m_partialUpdatePostponed) { + m_partialUpdatePostponed = false; + qCDebug(LOG) << "calling scanForTests with postponed files (onPartialParsingFinished)"; + scanForTests(m_postponedFiles.toList()); + } else { + m_dirty |= m_codeModelParsing; + if (m_dirty) { + emit parsingFailed(); + } else if (!m_singleShotScheduled) { + qCDebug(LOG) << "emitting parsingFinished" + << "(onPartialParsingFinished, nothing postponed, not dirty)"; + emit parsingFinished(); + } else { + qCDebug(LOG) << "not emitting parsingFinished" + << "(on PartialParsingFinished, singleshot scheduled)"; + } + } +} + +void TestCodeParser::updateUnnamedQuickTests(const QString &fileName, const QString &mainFile, + const QMap &functions) +{ + // if this test case was named before remove it + m_quickDocMap.remove(fileName); + emit testItemsRemoved(fileName, TestTreeModel::QuickTest); + + removeUnnamedQuickTestsByName(fileName); + foreach (const QString &functionName, functions.keys()) { + UnnamedQuickTestInfo info(functionName, fileName); + m_unnamedQuickDocList.append(info); + } + + emit unnamedQuickTestsUpdated(mainFile, functions); +} + +void TestCodeParser::updateModelAndCppDocMap(CPlusPlus::Document::Ptr document, + const QString &declaringFile, TestTreeItem *testItem) +{ + const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + const QString fileName = document->fileName(); + const QString testCaseName = testItem->name(); + QString proFile; + const QList ppList = cppMM->projectPart(fileName); + if (ppList.size()) + proFile = ppList.at(0)->projectFile; + + if (m_cppDocMap.contains(fileName)) { + QStringList files = { fileName }; + if (fileName != declaringFile) + files << declaringFile; + foreach (const QString &file, files) { + const bool setReferencingFile = (files.size() == 2 && file == declaringFile); + TestInfo testInfo(testCaseName, testItem->getChildNames(), + document->revision(), document->editorRevision()); + testInfo.setProFile(proFile); + if (setReferencingFile) + testInfo.setReferencingFile(fileName); + m_cppDocMap.insert(file, testInfo); + } + emit testItemModified(testItem, TestTreeModel::AutoTest, files); + } else { + emit testItemCreated(testItem, TestTreeModel::AutoTest); + TestInfo ti(testCaseName, testItem->getChildNames(), + document->revision(), document->editorRevision()); + ti.setProFile(proFile); + m_cppDocMap.insert(fileName, ti); + if (declaringFile != fileName) { + ti.setReferencingFile(fileName); + m_cppDocMap.insert(declaringFile, ti); + } + } +} + +void TestCodeParser::updateModelAndQuickDocMap(QmlJS::Document::Ptr document, + const QString &referencingFile, + TestTreeItem *testItem) +{ + const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + const QString fileName = document->fileName(); + QString proFile; + QList ppList = cppMM->projectPart(referencingFile); + if (ppList.size()) + proFile = ppList.at(0)->projectFile; + + if (m_quickDocMap.contains(fileName)) { + TestInfo testInfo(testItem->name(), testItem->getChildNames(), 0, document->editorRevision()); + testInfo.setReferencingFile(referencingFile); + testInfo.setProFile(proFile); + emit testItemModified(testItem, TestTreeModel::QuickTest, { fileName }); + m_quickDocMap.insert(fileName, testInfo); + } else { + // if it was formerly unnamed remove the respective items + removeUnnamedQuickTestsByName(fileName); + + const QString &filePath = testItem->filePath(); + TestInfo testInfo(testItem->name(), testItem->getChildNames(), 0, document->editorRevision()); + testInfo.setReferencingFile(referencingFile); + testInfo.setProFile(proFile); + emit testItemCreated(testItem, TestTreeModel::QuickTest); + m_quickDocMap.insert(filePath, testInfo); + } +} + +void TestCodeParser::updateGTests(const CPlusPlus::Document::Ptr &doc, + const QMap &tests) +{ + const QString &fileName = doc->fileName(); + removeGTestsByName(fileName); + + QString proFile; + const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + QList ppList = cppMM->projectPart(fileName); + if (ppList.size()) + proFile = ppList.at(0)->projectFile; + + foreach (const GTestCaseSpec &testSpec, tests.keys()) { + TestTreeItem *item = constructGTestTreeItem(fileName, testSpec, proFile, tests.value(testSpec)); + TestInfo info(item->name(), item->getChildNames(), doc->revision(), doc->editorRevision()); + info.setProFile(proFile); + foreach (const TestCodeLocationAndType &testSet, tests.value(testSpec)) { + GTestInfo gtestInfo(testSpec.testCaseName, testSet.m_name, fileName); + if (testSet.m_state & TestTreeItem::Disabled) + gtestInfo.setEnabled(false); + m_gtestDocList.append(gtestInfo); + } + emit testItemCreated(item, TestTreeModel::GoogleTest); + m_gtestDocMap.insert(fileName, info); + } +} + +void TestCodeParser::removeUnnamedQuickTestsByName(const QString &fileName) +{ + for (int i = m_unnamedQuickDocList.size() - 1; i >= 0; --i) { + if (m_unnamedQuickDocList.at(i).fileName() == fileName) + m_unnamedQuickDocList.removeAt(i); + } + emit unnamedQuickTestsRemoved(fileName); +} + +void TestCodeParser::removeGTestsByName(const QString &fileName) +{ + for (int i = m_gtestDocList.size() - 1; i >= 0; --i) + if (m_gtestDocList.at(i).fileName() == fileName) + m_gtestDocList.removeAt(i); + + emit gTestsRemoved(fileName); +} + +#ifdef WITH_TESTS +int TestCodeParser::autoTestsCount() const +{ + int count = 0; + foreach (const QString &file, m_cppDocMap.keys()) { + if (m_cppDocMap.value(file).referencingFile().isEmpty()) + ++count; + } + return count; +} + +int TestCodeParser::namedQuickTestsCount() const +{ + return m_quickDocMap.size(); +} + +int TestCodeParser::unnamedQuickTestsCount() const +{ + return m_unnamedQuickDocList.size(); +} + +int TestCodeParser::gtestNamesAndSetsCount() const +{ + return m_gtestDocList.size(); +} +#endif + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testcodeparser.h b/src/plugins/autotest/testcodeparser.h new file mode 100644 index 00000000000..0b3d76ba6cc --- /dev/null +++ b/src/plugins/autotest/testcodeparser.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTCODEPARSER_H +#define TESTCODEPARSER_H + +#include "testtreeitem.h" +#include "testtreemodel.h" + +#include + +#include + +#include +#include + +namespace Core { +class Id; +} + +namespace Autotest { +namespace Internal { + +struct TestCodeLocationAndType; +class TestInfo; +class UnnamedQuickTestInfo; +class GTestInfo; +struct GTestCaseSpec; + +class TestCodeParser : public QObject +{ + Q_OBJECT +public: + enum State { + Idle, + PartialParse, + FullParse, + Disabled + }; + + explicit TestCodeParser(TestTreeModel *parent = 0); + virtual ~TestCodeParser(); + void setState(State state); + State state() const { return m_parserState; } + void setDirty() { m_dirty = true; } + +#ifdef WITH_TESTS + int autoTestsCount() const; + int namedQuickTestsCount() const; + int unnamedQuickTestsCount() const; + int gtestNamesAndSetsCount() const; +#endif + +signals: + void cacheCleared(); + void testItemCreated(TestTreeItem *item, TestTreeModel::Type type); + void testItemModified(TestTreeItem *tItem, TestTreeModel::Type type, const QStringList &file); + void testItemsRemoved(const QString &filePath, TestTreeModel::Type type); + void unnamedQuickTestsUpdated(const QString &mainFile, + const QMap &functions); + void unnamedQuickTestsRemoved(const QString &filePath); + void gTestsRemoved(const QString &filePath); + void parsingStarted(); + void parsingFinished(); + void parsingFailed(); + void partialParsingFinished(); + +public slots: + void emitUpdateTestTree(); + void updateTestTree(); + void checkDocumentForTestCode(CPlusPlus::Document::Ptr document); + void handleQtQuickTest(CPlusPlus::Document::Ptr document); + void handleGTest(const QString &filePath); + + void onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document); + void onQmlDocumentUpdated(const QmlJS::Document::Ptr &document); + void onStartupProjectChanged(ProjectExplorer::Project *); + void onProjectPartsUpdated(ProjectExplorer::Project *project); + void removeFiles(const QStringList &files); + +private: + bool postponed(const QStringList &fileList); + void scanForTests(const QStringList &fileList = QStringList()); + void clearCache(); + void removeTestsIfNecessary(const QString &fileName); + + void onTaskStarted(Core::Id type); + void onAllTasksFinished(Core::Id type); + void onFinished(); + void onPartialParsingFinished(); + void updateUnnamedQuickTests(const QString &fileName, const QString &mainFile, + const QMap &functions); + void updateModelAndCppDocMap(CPlusPlus::Document::Ptr document, + const QString &declaringFile, TestTreeItem *testItem); + void updateModelAndQuickDocMap(QmlJS::Document::Ptr document, + const QString &referencingFile, TestTreeItem *testItem); + void updateGTests(const CPlusPlus::Document::Ptr &doc, + const QMap &tests); + void removeUnnamedQuickTestsByName(const QString &fileName); + void removeGTestsByName(const QString &fileName); + + TestTreeModel *m_model; + QMap m_cppDocMap; + QMap m_quickDocMap; + QMap m_gtestDocMap; + QList m_unnamedQuickDocList; + QList m_gtestDocList; + bool m_codeModelParsing; + bool m_fullUpdatePostponed; + bool m_partialUpdatePostponed; + bool m_dirty; + bool m_singleShotScheduled; + QSet m_postponedFiles; + State m_parserState; +}; + +} // namespace Internal +} // Autotest + +#endif // TESTCODEPARSER_H diff --git a/src/plugins/autotest/testconfiguration.cpp b/src/plugins/autotest/testconfiguration.cpp new file mode 100644 index 00000000000..063aa5980ea --- /dev/null +++ b/src/plugins/autotest/testconfiguration.cpp @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "testconfiguration.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace ProjectExplorer; + +namespace Autotest { +namespace Internal { + +TestConfiguration::TestConfiguration(const QString &testClass, const QStringList &testCases, + int testCaseCount, QObject *parent) + : QObject(parent), + m_testClass(testClass), + m_testCases(testCases), + m_testCaseCount(testCaseCount), + m_unnamedOnly(false), + m_project(0), + m_guessedConfiguration(false), + m_type(TestTypeQt) +{ + if (testCases.size() != 0) + m_testCaseCount = testCases.size(); +} + +TestConfiguration::~TestConfiguration() +{ + m_testCases.clear(); +} + +void basicProjectInformation(Project *project, const QString &mainFilePath, QString *proFile, + QString *displayName, Project **targetProject) +{ + CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + QList projParts = cppMM->projectInfo(project).projectParts(); + + foreach (const CppTools::ProjectPart::Ptr &part, projParts) { + foreach (const CppTools::ProjectFile currentFile, part->files) { + if (currentFile.path == mainFilePath) { + *proFile = part->projectFile; + *displayName = part->displayName; + *targetProject = part->project; + return; + } + } + } +} + +void basicProjectInformation(Project *project, const QString &proFile, QString *displayName, + Project **targetProject) +{ + CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + QList projParts = cppMM->projectInfo(project).projectParts(); + + foreach (const CppTools::ProjectPart::Ptr &part, projParts) { + if (part->projectFile == proFile) { + *displayName = part->displayName; + *targetProject = part->project; + return; + } + } +} + +void extractEnvironmentInformation(LocalApplicationRunConfiguration *localRunConfiguration, + QString *workDir, Utils::Environment *env) +{ + *workDir = Utils::FileUtils::normalizePathName(localRunConfiguration->workingDirectory()); + if (auto environmentAspect = localRunConfiguration->extraAspect()) + *env = environmentAspect->environment(); +} + +void TestConfiguration::completeTestInformation() +{ + QTC_ASSERT(!m_mainFilePath.isEmpty() || !m_proFile.isEmpty(), return); + + typedef LocalApplicationRunConfiguration LocalRunConfig; + + Project *project = SessionManager::startupProject(); + if (!project) + return; + + QString targetFile; + QString targetName; + QString workDir; + QString proFile = m_proFile; + QString displayName; + Project *targetProject = 0; + Utils::Environment env; + bool hasDesktopTarget = false; + bool guessedRunConfiguration = false; + setProject(0); + + if (m_proFile.isEmpty()) + basicProjectInformation(project, m_mainFilePath, &proFile, &displayName, &targetProject); + else + basicProjectInformation(project, proFile, &displayName, &targetProject); + + Target *target = project->activeTarget(); + if (!target) + return; + + BuildTargetInfoList appTargets = target->applicationTargets(); + foreach (const BuildTargetInfo &bti, appTargets.list) { + // some project manager store line/column information as well inside ProjectPart + if (bti.isValid() && proFile.startsWith(bti.projectFilePath.toString())) { + targetFile = Utils::HostOsInfo::withExecutableSuffix(bti.targetFilePath.toString()); + targetName = bti.targetName; + break; + } + } + + QList rcs = target->runConfigurations(); + foreach (RunConfiguration *rc, rcs) { + auto config = qobject_cast(rc); + if (config && config->executable() == targetFile) { + extractEnvironmentInformation(config, &workDir, &env); + hasDesktopTarget = true; + break; + } + } + + // if we could not figure out the run configuration + // try to use the run configuration of the parent project + if (!hasDesktopTarget && targetProject && !targetFile.isEmpty()) { + if (auto config = qobject_cast(target->activeRunConfiguration())) { + extractEnvironmentInformation(config, &workDir, &env); + hasDesktopTarget = true; + guessedRunConfiguration = true; + } + } + + setProFile(proFile); + setDisplayName(displayName); + + if (hasDesktopTarget) { + setTargetFile(targetFile); + setTargetName(targetName); + setWorkingDirectory(workDir); + setEnvironment(env); + setProject(project); + setGuessedConfiguration(guessedRunConfiguration); + } +} + + +/** + * @brief sets the test cases for this test configuration. + * + * Watch out for special handling of test configurations, because this method also + * updates the test case count to the current size of \a testCases. + * + * @param testCases list of names of the test functions / test cases + */ +void TestConfiguration::setTestCases(const QStringList &testCases) +{ + m_testCases.clear(); + m_testCases << testCases; + m_testCaseCount = m_testCases.size(); +} + +void TestConfiguration::setTestCaseCount(int count) +{ + m_testCaseCount = count; +} + +void TestConfiguration::setMainFilePath(const QString &mainFile) +{ + m_mainFilePath = mainFile; +} + +void TestConfiguration::setTargetFile(const QString &targetFile) +{ + m_targetFile = targetFile; +} + +void TestConfiguration::setTargetName(const QString &targetName) +{ + m_targetName = targetName; +} + +void TestConfiguration::setProFile(const QString &proFile) +{ + m_proFile = proFile; +} + +void TestConfiguration::setWorkingDirectory(const QString &workingDirectory) +{ + m_workingDir = workingDirectory; +} + +void TestConfiguration::setDisplayName(const QString &displayName) +{ + m_displayName = displayName; +} + +void TestConfiguration::setEnvironment(const Utils::Environment &env) +{ + m_environment = env; +} + +void TestConfiguration::setProject(Project *project) +{ + m_project = project; +} + +void TestConfiguration::setUnnamedOnly(bool unnamedOnly) +{ + m_unnamedOnly = unnamedOnly; +} + +void TestConfiguration::setGuessedConfiguration(bool guessed) +{ + m_guessedConfiguration = guessed; +} + +void TestConfiguration::setTestType(TestType type) +{ + m_type = type; +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testconfiguration.h b/src/plugins/autotest/testconfiguration.h new file mode 100644 index 00000000000..632ce69b720 --- /dev/null +++ b/src/plugins/autotest/testconfiguration.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTCONFIGURATION_H +#define TESTCONFIGURATION_H + +#include "autotestconstants.h" + +#include +#include + +#include +#include +#include + +namespace Autotest { +namespace Internal { + +class TestConfiguration : public QObject + +{ + Q_OBJECT +public: + explicit TestConfiguration(const QString &testClass, const QStringList &testCases, + int testCaseCount = 0, QObject *parent = 0); + ~TestConfiguration(); + + void completeTestInformation(); + + void setTestCases(const QStringList &testCases); + void setTestCaseCount(int count); + void setMainFilePath(const QString &mainFile); + void setTargetFile(const QString &targetFile); + void setTargetName(const QString &targetName); + void setProFile(const QString &proFile); + void setWorkingDirectory(const QString &workingDirectory); + void setDisplayName(const QString &displayName); + void setEnvironment(const Utils::Environment &env); + void setProject(ProjectExplorer::Project *project); + void setUnnamedOnly(bool unnamedOnly); + void setGuessedConfiguration(bool guessed); + void setTestType(TestType type); + + QString testClass() const { return m_testClass; } + QStringList testCases() const { return m_testCases; } + int testCaseCount() const { return m_testCaseCount; } + QString proFile() const { return m_proFile; } + QString targetFile() const { return m_targetFile; } + QString targetName() const { return m_targetName; } + QString workingDirectory() const { return m_workingDir; } + QString displayName() const { return m_displayName; } + Utils::Environment environment() const { return m_environment; } + ProjectExplorer::Project *project() const { return m_project.data(); } + bool unnamedOnly() const { return m_unnamedOnly; } + bool guessedConfiguration() const { return m_guessedConfiguration; } + TestType testType() const { return m_type; } + +private: + QString m_testClass; + QStringList m_testCases; + int m_testCaseCount; + QString m_mainFilePath; + bool m_unnamedOnly; + QString m_proFile; + QString m_targetFile; + QString m_targetName; + QString m_workingDir; + QString m_displayName; + Utils::Environment m_environment; + QPointer m_project; + bool m_guessedConfiguration; + TestType m_type; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTCONFIGURATION_H diff --git a/src/plugins/autotest/testinfo.cpp b/src/plugins/autotest/testinfo.cpp new file mode 100644 index 00000000000..859df4ee3af --- /dev/null +++ b/src/plugins/autotest/testinfo.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "testinfo.h" + +namespace Autotest { +namespace Internal { + +TestInfo::TestInfo(const QString &className, const QStringList &functions, unsigned revision, + unsigned editorRevision) + : m_className(className), + m_functions(functions), + m_revision(revision), + m_editorRevision(editorRevision) +{ +} + +TestInfo::~TestInfo() +{ + m_functions.clear(); +} + +UnnamedQuickTestInfo::UnnamedQuickTestInfo(const QString &function, const QString &fileName) + : m_function(function), + m_fileName(fileName) +{ +} + +GTestInfo::GTestInfo(const QString &caseName, const QString &setName, const QString &file) + : m_caseName(caseName), + m_setName(setName), + m_fileName(file), + m_enabled(true) +{ +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testinfo.h b/src/plugins/autotest/testinfo.h new file mode 100644 index 00000000000..5e8e9f355fd --- /dev/null +++ b/src/plugins/autotest/testinfo.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTINFO_H +#define TESTINFO_H + +#include + +namespace Autotest { +namespace Internal { + +class TestInfo +{ +public: + explicit TestInfo(const QString &className = QString(), + const QStringList &functions = QStringList(), + unsigned revision = 0, unsigned editorRevision = 0); + + ~TestInfo(); + const QString testClass() const { return m_className; } + void setTestClass(const QString &className) { m_className = className; } + const QStringList testFunctions() const { return m_functions; } + void setTestFunctions(const QStringList &functions) { m_functions = functions; } + unsigned revision() const { return m_revision; } + void setRevision(unsigned revision) { m_revision = revision; } + unsigned editorRevision() const { return m_editorRevision; } + void setEditorRevision(unsigned editorRevision) { m_editorRevision = editorRevision; } + const QString referencingFile() const { return m_referencingFile; } + void setReferencingFile(const QString &refFile) { m_referencingFile = refFile; } + const QString proFile() const { return m_proFile; } + void setProFile(const QString &proFile) { m_proFile = proFile; } + +private: + QString m_className; + QStringList m_functions; + unsigned m_revision; + unsigned m_editorRevision; + QString m_referencingFile; + QString m_proFile; +}; + +class UnnamedQuickTestInfo { +public: + explicit UnnamedQuickTestInfo(const QString &function = QString(), + const QString& fileName = QString()); + ~UnnamedQuickTestInfo() {} + + const QString function() const { return m_function; } + void setFunction(const QString &function) { m_function = function; } + const QString fileName() const { return m_fileName; } + void setFileName(const QString &fileName) { m_fileName = fileName; } + +private: + QString m_function; + QString m_fileName; +}; + +class GTestInfo { +public: + explicit GTestInfo(const QString &caseName, const QString &setName, const QString &file); + + const QString caseName() const { return m_caseName; } + void setCaseName(const QString &caseName) { m_caseName = caseName; } + const QString setName() const { return m_setName; } + void setSetName(const QString &setName) { m_setName = setName; } + const QString fileName() const { return m_fileName; } + void setFileName(const QString &fileName) { m_fileName = fileName; } + bool isEnabled() const { return m_enabled; } + void setEnabled(bool enabled) { m_enabled = enabled; } + +private: + QString m_caseName; + QString m_setName; + QString m_fileName; + bool m_enabled; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTINFO_H diff --git a/src/plugins/autotest/testnavigationwidget.cpp b/src/plugins/autotest/testnavigationwidget.cpp new file mode 100644 index 00000000000..a8432e70bee --- /dev/null +++ b/src/plugins/autotest/testnavigationwidget.cpp @@ -0,0 +1,293 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "testnavigationwidget.h" +#include "testtreemodel.h" +#include "testtreeview.h" +#include "testtreeitemdelegate.h" +#include "testcodeparser.h" +#include "testrunner.h" +#include "autotestconstants.h" +#include "autotesticons.h" +#include "testtreeitem.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Autotest { +namespace Internal { + +TestNavigationWidget::TestNavigationWidget(QWidget *parent) : + QWidget(parent) +{ + setWindowTitle(tr("Tests")); + m_model = TestTreeModel::instance(); + m_sortFilterModel = new TestTreeSortFilterModel(m_model, m_model); + m_sortFilterModel->setDynamicSortFilter(true); + m_view = new TestTreeView(this); + m_view->setModel(m_sortFilterModel); + m_view->setSortingEnabled(true); + m_view->setItemDelegate(new TestTreeItemDelegate(this)); + + QVBoxLayout *layout = new QVBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(Core::ItemViewFind::createSearchableWrapper(m_view)); + setLayout(layout); + + connect(m_view, &TestTreeView::activated, this, &TestNavigationWidget::onItemActivated); + + m_progressIndicator = new Utils::ProgressIndicator(Utils::ProgressIndicator::Medium, this); + m_progressIndicator->attachToWidget(m_view); + m_progressIndicator->hide(); + + m_progressTimer = new QTimer(this); + m_progressTimer->setSingleShot(true); + m_progressTimer->setInterval(100); // don't display indicator if progress takes less than 100ms + + connect(m_model->parser(), &TestCodeParser::parsingStarted, + this, &TestNavigationWidget::onParsingStarted); + connect(m_model->parser(), &TestCodeParser::parsingFinished, + this, &TestNavigationWidget::onParsingFinished); + connect(m_model->parser(), &TestCodeParser::parsingFailed, + this, &TestNavigationWidget::onParsingFinished); + connect(m_progressTimer, &QTimer::timeout, + m_progressIndicator, &Utils::ProgressIndicator::show); +} + +TestNavigationWidget::~TestNavigationWidget() +{ + m_model->disableParsing(); +} + +void TestNavigationWidget::contextMenuEvent(QContextMenuEvent *event) +{ + const bool enabled = !TestRunner::instance()->isTestRunning() + && m_model->parser()->state() == TestCodeParser::Idle; + const bool hasTests = m_model->hasTests(); + + QMenu menu; + QAction *runThisTest = 0; + const QModelIndexList list = m_view->selectionModel()->selectedIndexes(); + if (list.size() == 1) { + const QModelIndex index = list.first(); + QRect rect(m_view->visualRect(index)); + if (rect.contains(event->pos())) { + // do not provide this menu entry for unnamed Quick Tests as it makes no sense + int type = index.data(TypeRole).toInt(); + const QString &unnamed = tr(Constants::UNNAMED_QUICKTESTS); + if ((type == TestTreeItem::TestFunction && index.parent().data().toString() != unnamed) + || (type == TestTreeItem::TestClass && index.data().toString() != unnamed) + || (type == TestTreeItem::TestDataTag) + || (type == TestTreeItem::GTestCase) + || (type == TestTreeItem::GTestCaseParameterized) + || (type == TestTreeItem::GTestName)) { + runThisTest = new QAction(tr("Run This Test"), &menu); + runThisTest->setEnabled(enabled); + connect(runThisTest, &QAction::triggered, + this, &TestNavigationWidget::onRunThisTestTriggered); + } + } + } + + QAction *runAll = Core::ActionManager::command(Constants::ACTION_RUN_ALL_ID)->action(); + QAction *runSelected = Core::ActionManager::command(Constants::ACTION_RUN_SELECTED_ID)->action(); + QAction *selectAll = new QAction(tr("Select All"), &menu); + QAction *deselectAll = new QAction(tr("Deselect All"), &menu); + // TODO remove? + QAction *rescan = Core::ActionManager::command(Constants::ACTION_SCAN_ID)->action(); + + connect(selectAll, &QAction::triggered, m_view, &TestTreeView::selectAll); + connect(deselectAll, &QAction::triggered, m_view, &TestTreeView::deselectAll); + + runAll->setEnabled(enabled && hasTests); + runSelected->setEnabled(enabled && hasTests); + selectAll->setEnabled(enabled && hasTests); + deselectAll->setEnabled(enabled && hasTests); + rescan->setEnabled(enabled); + + if (runThisTest) { + menu.addAction(runThisTest); + menu.addSeparator(); + } + menu.addAction(runAll); + menu.addAction(runSelected); + menu.addSeparator(); + menu.addAction(selectAll); + menu.addAction(deselectAll); + menu.addSeparator(); + menu.addAction(rescan); + + menu.exec(mapToGlobal(event->pos())); +} + +QList TestNavigationWidget::createToolButtons() +{ + QList list; + + m_filterButton = new QToolButton(m_view); + m_filterButton->setIcon(Core::Icons::FILTER.icon()); + m_filterButton->setToolTip(tr("Filter Test Tree")); + m_filterButton->setProperty("noArrow", true); + m_filterButton->setAutoRaise(true); + m_filterButton->setPopupMode(QToolButton::InstantPopup); + m_filterMenu = new QMenu(m_filterButton); + initializeFilterMenu(); + connect(m_filterMenu, &QMenu::triggered, this, &TestNavigationWidget::onFilterMenuTriggered); + m_filterButton->setMenu(m_filterMenu); + + m_sortAlphabetically = true; + m_sort = new QToolButton(this); + m_sort->setIcon(Icons::SORT_NATURALLY.icon()); + m_sort->setToolTip(tr("Sort Naturally")); + + QToolButton *expand = new QToolButton(this); + expand->setIcon(Icons::EXPAND.icon()); + expand->setToolTip(tr("Expand All")); + + QToolButton *collapse = new QToolButton(this); + collapse->setIcon(Icons::COLLAPSE.icon()); + collapse->setToolTip(tr("Collapse All")); + + connect(expand, &QToolButton::clicked, m_view, &TestTreeView::expandAll); + connect(collapse, &QToolButton::clicked, m_view, &TestTreeView::collapseAll); + connect(m_sort, &QToolButton::clicked, this, &TestNavigationWidget::onSortClicked); + + list << m_filterButton << m_sort << expand << collapse; + return list; +} + +void TestNavigationWidget::onItemActivated(const QModelIndex &index) +{ + const TextEditor::TextEditorWidget::Link link + = index.data(LinkRole).value(); + if (link.hasValidTarget()) { + Core::EditorManager::openEditorAt(link.targetFileName, link.targetLine, + link.targetColumn); + } +} + +void TestNavigationWidget::onSortClicked() +{ + if (m_sortAlphabetically) { + m_sort->setIcon(Icons::SORT_ALPHABETICALLY.icon()); + m_sort->setToolTip(tr("Sort Alphabetically")); + m_sortFilterModel->setSortMode(TestTreeSortFilterModel::Naturally); + } else { + m_sort->setIcon(Icons::SORT_NATURALLY.icon()); + m_sort->setToolTip(tr("Sort Naturally")); + m_sortFilterModel->setSortMode(TestTreeSortFilterModel::Alphabetically); + } + m_sortAlphabetically = !m_sortAlphabetically; +} + +void TestNavigationWidget::onFilterMenuTriggered(QAction *action) +{ + m_sortFilterModel->toggleFilter( + TestTreeSortFilterModel::toFilterMode(action->data().value())); +} + +void TestNavigationWidget::onParsingStarted() +{ + m_progressTimer->start(); +} + +void TestNavigationWidget::onParsingFinished() +{ + m_progressTimer->stop(); + m_progressIndicator->hide(); +} + +void TestNavigationWidget::initializeFilterMenu() +{ + QAction *action = new QAction(m_filterMenu); + action->setText(tr("Show Init and Cleanup Functions")); + action->setCheckable(true); + action->setChecked(false); + action->setData(TestTreeSortFilterModel::ShowInitAndCleanup); + m_filterMenu->addAction(action); + action = new QAction(m_filterMenu); + action->setText(tr("Show Data Functions")); + action->setCheckable(true); + action->setChecked(false); + action->setData(TestTreeSortFilterModel::ShowTestData); + m_filterMenu->addAction(action); +} + +void TestNavigationWidget::onRunThisTestTriggered() +{ + const QModelIndexList selected = m_view->selectionModel()->selectedIndexes(); + // paranoia + if (selected.isEmpty()) + return; + const QModelIndex sourceIndex = m_sortFilterModel->mapToSource(selected.first()); + if (!sourceIndex.isValid()) + return; + + TestTreeItem *item = static_cast(sourceIndex.internalPointer()); + if (item->type() == TestTreeItem::TestClass || item->type() == TestTreeItem::TestFunction + || item->type() == TestTreeItem::TestDataTag + || item->type() == TestTreeItem::GTestCase + || item->type() == TestTreeItem::GTestCaseParameterized + || item->type() == TestTreeItem::GTestName) { + if (TestConfiguration *configuration = m_model->getTestConfiguration(item)) { + TestRunner *runner = TestRunner::instance(); + runner->setSelectedTests( {configuration} ); + runner->prepareToRunTests(); + } + } +} + +TestNavigationWidgetFactory::TestNavigationWidgetFactory() +{ + setDisplayName(tr("Tests")); + setId(Autotest::Constants::AUTOTEST_ID); + setPriority(666); +} + +Core::NavigationView TestNavigationWidgetFactory::createWidget() +{ + TestNavigationWidget *treeViewWidget = new TestNavigationWidget; + Core::NavigationView view; + view.widget = treeViewWidget; + view.dockToolBarWidgets = treeViewWidget->createToolButtons(); + TestTreeModel::instance()->enableParsing(); + return view; +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testnavigationwidget.h b/src/plugins/autotest/testnavigationwidget.h new file mode 100644 index 00000000000..5f92909b93d --- /dev/null +++ b/src/plugins/autotest/testnavigationwidget.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTNAVIGATIONWIDGETTREEVIEW_H +#define TESTNAVIGATIONWIDGETTREEVIEW_H + +#include + +#include + +QT_BEGIN_NAMESPACE +class QAction; +class QMenu; +class QTimer; +class QToolButton; +QT_END_NAMESPACE + +namespace Core { +class IContext; +} + +namespace Utils { +class ProgressIndicator; +} + +namespace Autotest { +namespace Internal { + +class TestTreeModel; +class TestTreeSortFilterModel; +class TestTreeView; + +class TestNavigationWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TestNavigationWidget(QWidget *parent = 0); + ~TestNavigationWidget(); + void contextMenuEvent(QContextMenuEvent *event); + QList createToolButtons(); + +signals: + +public slots: + +private slots: + void onItemActivated(const QModelIndex &index); + void onSortClicked(); + void onFilterMenuTriggered(QAction *action); + void onParsingStarted(); + void onParsingFinished(); + +private: + void initializeFilterMenu(); + void onRunThisTestTriggered(); + + TestTreeModel *m_model; + TestTreeSortFilterModel *m_sortFilterModel; + TestTreeView *m_view; + QToolButton *m_sort; + QToolButton *m_filterButton; + QMenu *m_filterMenu; + bool m_sortAlphabetically; + Utils::ProgressIndicator *m_progressIndicator; + QTimer *m_progressTimer; +}; + +class TestNavigationWidgetFactory : public Core::INavigationWidgetFactory +{ + Q_OBJECT + +public: + TestNavigationWidgetFactory(); + +private: + Core::NavigationView createWidget(); + +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTNAVIGATIONWIDGETTREEVIEW_H diff --git a/src/plugins/autotest/testoutputreader.cpp b/src/plugins/autotest/testoutputreader.cpp new file mode 100644 index 00000000000..2a97f0c818a --- /dev/null +++ b/src/plugins/autotest/testoutputreader.cpp @@ -0,0 +1,424 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "testoutputreader.h" +#include "testresult.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace Autotest { +namespace Internal { + +static QString decode(const QString& original) +{ + QString result(original); + static QRegExp regex(QLatin1String("&#((x[0-9A-F]+)|([0-9]+));"), Qt::CaseInsensitive); + regex.setMinimal(true); + + int pos = 0; + while ((pos = regex.indexIn(original, pos)) != -1) { + const QString value = regex.cap(1); + if (value.startsWith(QLatin1Char('x'))) + result.replace(regex.cap(0), QChar(value.mid(1).toInt(0, 16))); + else + result.replace(regex.cap(0), QChar(value.toInt(0, 10))); + pos += regex.matchedLength(); + } + + return result; +} + +static QString constructSourceFilePath(const QString &path, const QString &filePath, + const QString &app) +{ + if (Utils::HostOsInfo::isMacHost() && !app.isEmpty()) { + const QString fileName(QFileInfo(app).fileName()); + return QFileInfo(path.left(path.lastIndexOf(fileName + QLatin1String(".app"))), filePath) + .canonicalFilePath(); + } + return QFileInfo(path, filePath).canonicalFilePath(); +} + +// adapted from qplaintestlogger.cpp +static QString formatResult(double value) +{ + //NAN is not supported with visual studio 2010 + if (value < 0)// || value == NAN) + return QLatin1String("NAN"); + if (value == 0) + return QLatin1String("0"); + + int significantDigits = 0; + qreal divisor = 1; + + while (value / divisor >= 1) { + divisor *= 10; + ++significantDigits; + } + + QString beforeDecimalPoint = QString::number(value, 'f', 0); + QString afterDecimalPoint = QString::number(value, 'f', 20); + afterDecimalPoint.remove(0, beforeDecimalPoint.count() + 1); + + const int beforeUse = qMin(beforeDecimalPoint.count(), significantDigits); + const int beforeRemove = beforeDecimalPoint.count() - beforeUse; + + beforeDecimalPoint.chop(beforeRemove); + for (int i = 0; i < beforeRemove; ++i) + beforeDecimalPoint.append(QLatin1Char('0')); + + int afterUse = significantDigits - beforeUse; + if (beforeDecimalPoint == QLatin1String("0") && !afterDecimalPoint.isEmpty()) { + ++afterUse; + int i = 0; + while (i < afterDecimalPoint.count() && afterDecimalPoint.at(i) == QLatin1Char('0')) + ++i; + afterUse += i; + } + + const int afterRemove = afterDecimalPoint.count() - afterUse; + afterDecimalPoint.chop(afterRemove); + + QString result = beforeDecimalPoint; + if (afterUse > 0) + result.append(QLatin1Char('.')); + result += afterDecimalPoint; + + return result; +} + +static QString constructBenchmarkInformation(const QString &metric, double value, int iterations) +{ + QString metricsText; + if (metric == QLatin1String("WalltimeMilliseconds")) // default + metricsText = QLatin1String("msecs"); + else if (metric == QLatin1String("CPUTicks")) // -tickcounter + metricsText = QLatin1String("CPU ticks"); + else if (metric == QLatin1String("Events")) // -eventcounter + metricsText = QLatin1String("events"); + else if (metric == QLatin1String("InstructionReads")) // -callgrind + metricsText = QLatin1String("instruction reads"); + else if (metric == QLatin1String("CPUCycles")) // -perf + metricsText = QLatin1String("CPU cycles"); + return QObject::tr("%1 %2 per iteration (total: %3, iterations: %4)") + .arg(formatResult(value)) + .arg(metricsText) + .arg(formatResult(value * (double)iterations)) + .arg(iterations); +} + +TestOutputReader::TestOutputReader(QFutureInterface futureInterface, + QProcess *testApplication) + : m_futureInterface(futureInterface) + , m_testApplication(testApplication) +{ + connect(m_testApplication, &QProcess::readyRead, this, &TestOutputReader::processOutput); +} + +QtTestOutputReader::QtTestOutputReader(QFutureInterface futureInterface, + QProcess *testApplication) + : TestOutputReader(futureInterface, testApplication) +{ +} + +void QtTestOutputReader::processOutput() +{ + if (!m_testApplication || m_testApplication->state() != QProcess::Running) + return; + static QStringList validEndTags = { QStringLiteral("Incident"), + QStringLiteral("Message"), + QStringLiteral("BenchmarkResult"), + QStringLiteral("QtVersion"), + QStringLiteral("QtBuild"), + QStringLiteral("QTestVersion") }; + + while (m_testApplication->canReadLine()) { + m_xmlReader.addData(m_testApplication->readLine()); + while (!m_xmlReader.atEnd()) { + if (m_futureInterface.isCanceled()) + return; + QXmlStreamReader::TokenType token = m_xmlReader.readNext(); + switch (token) { + case QXmlStreamReader::StartDocument: + m_className.clear(); + break; + case QXmlStreamReader::EndDocument: + m_xmlReader.clear(); + return; + case QXmlStreamReader::StartElement: { + const QString currentTag = m_xmlReader.name().toString(); + if (currentTag == QStringLiteral("TestCase")) { + m_className = m_xmlReader.attributes().value(QStringLiteral("name")).toString(); + QTC_ASSERT(!m_className.isEmpty(), continue); + auto testResult = new QTestResult(m_className); + testResult->setResult(Result::MessageTestCaseStart); + testResult->setDescription(tr("Executing test case %1").arg(m_className)); + m_futureInterface.reportResult(testResult); + } else if (currentTag == QStringLiteral("TestFunction")) { + m_testCase = m_xmlReader.attributes().value(QStringLiteral("name")).toString(); + QTC_ASSERT(!m_testCase.isEmpty(), continue); + auto testResult = new QTestResult(); + testResult->setResult(Result::MessageCurrentTest); + testResult->setDescription(tr("Entering test function %1::%2").arg(m_className, + m_testCase)); + m_futureInterface.reportResult(testResult); + } else if (currentTag == QStringLiteral("Duration")) { + m_duration = m_xmlReader.attributes().value(QStringLiteral("msecs")).toString(); + QTC_ASSERT(!m_duration.isEmpty(), continue); + } else if (currentTag == QStringLiteral("Message") + || currentTag == QStringLiteral("Incident")) { + m_dataTag.clear(); + m_description.clear(); + m_duration.clear(); + m_file.clear(); + m_result = Result::Invalid; + m_lineNumber = 0; + const QXmlStreamAttributes &attributes = m_xmlReader.attributes(); + m_result = TestResult::resultFromString( + attributes.value(QStringLiteral("type")).toString()); + m_file = decode(attributes.value(QStringLiteral("file")).toString()); + if (!m_file.isEmpty()) { + const QString base = QFileInfo(m_testApplication->program()).absolutePath(); + m_file = constructSourceFilePath(base, m_file, + m_testApplication->program()); + } + m_lineNumber = attributes.value(QStringLiteral("line")).toInt(); + } else if (currentTag == QStringLiteral("BenchmarkResult")) { + const QXmlStreamAttributes &attributes = m_xmlReader.attributes(); + const QString metric = attributes.value(QStringLiteral("metrics")).toString(); + const double value = attributes.value(QStringLiteral("value")).toDouble(); + const int iterations = attributes.value(QStringLiteral("iterations")).toInt(); + m_description = constructBenchmarkInformation(metric, value, iterations); + m_result = Result::Benchmark; + } else if (currentTag == QStringLiteral("DataTag")) { + m_cdataMode = DataTag; + } else if (currentTag == QStringLiteral("Description")) { + m_cdataMode = Description; + } else if (currentTag == QStringLiteral("QtVersion")) { + m_result = Result::MessageInternal; + m_cdataMode = QtVersion; + } else if (currentTag == QStringLiteral("QtBuild")) { + m_result = Result::MessageInternal; + m_cdataMode = QtBuild; + } else if (currentTag == QStringLiteral("QTestVersion")) { + m_result = Result::MessageInternal; + m_cdataMode = QTestVersion; + } + break; + } + case QXmlStreamReader::Characters: { + QStringRef text = m_xmlReader.text().trimmed(); + if (text.isEmpty()) + break; + + switch (m_cdataMode) { + case DataTag: + m_dataTag = text.toString(); + break; + case Description: + if (!m_description.isEmpty()) + m_description.append(QLatin1Char('\n')); + m_description.append(text); + break; + case QtVersion: + m_description = tr("Qt version: %1").arg(text.toString()); + break; + case QtBuild: + m_description = tr("Qt build: %1").arg(text.toString()); + break; + case QTestVersion: + m_description = tr("QTest version: %1").arg(text.toString()); + break; + default: + QString message = QString::fromLatin1("unexpected cdatamode %1 for text \"%2\"") + .arg(m_cdataMode) + .arg(text.toString()); + QTC_ASSERT(false, qWarning() << message); + break; + } + break; + } + case QXmlStreamReader::EndElement: { + m_cdataMode = None; + const QStringRef currentTag = m_xmlReader.name(); + if (currentTag == QStringLiteral("TestFunction")) { + if (!m_duration.isEmpty()) { + auto testResult = new QTestResult(m_className); + testResult->setTestCase(m_testCase); + testResult->setResult(Result::MessageInternal); + testResult->setDescription(tr("Execution took %1 ms.").arg(m_duration)); + m_futureInterface.reportResult(testResult); + } + m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); + } else if (currentTag == QStringLiteral("TestCase")) { + auto testResult = new QTestResult(m_className); + testResult->setResult(Result::MessageTestCaseEnd); + testResult->setDescription( + m_duration.isEmpty() ? tr("Test finished.") + : tr("Test execution took %1 ms.").arg(m_duration)); + m_futureInterface.reportResult(testResult); + } else if (validEndTags.contains(currentTag.toString())) { + auto testResult = new QTestResult(m_className); + testResult->setTestCase(m_testCase); + testResult->setDataTag(m_dataTag); + testResult->setResult(m_result); + testResult->setFileName(m_file); + testResult->setLine(m_lineNumber); + testResult->setDescription(m_description); + m_futureInterface.reportResult(testResult); + } + break; + } + default: + break; + } + } + } +} + +GTestOutputReader::GTestOutputReader(QFutureInterface futureInterface, + QProcess *testApplication) + : TestOutputReader(futureInterface, testApplication) +{ +} + +void GTestOutputReader::processOutput() +{ + if (!m_testApplication || m_testApplication->state() != QProcess::Running) + return; + + static QRegExp newTestStarts(QStringLiteral("^\\[-{10}\\] \\d+ tests? from (.*)$")); + static QRegExp testEnds(QStringLiteral("^\\[-{10}\\] \\d+ tests? from (.*) \\((.*)\\)$")); + static QRegExp newTestSetStarts(QStringLiteral("^\\[ RUN \\] (.*)$")); + static QRegExp testSetSuccess(QStringLiteral("^\\[ OK \\] (.*) \\((.*)\\)$")); + static QRegExp testSetFail(QStringLiteral("^\\\[ FAILED \\] (.*) \\((.*)\\)$")); + static QRegExp disabledTests(QStringLiteral("^ YOU HAVE (\\d+) DISABLED TESTS?$")); + + while (m_testApplication->canReadLine()) { + if (m_futureInterface.isCanceled()) + return; + QByteArray read = m_testApplication->readLine(); + if (!m_unprocessed.isEmpty()) { + read = m_unprocessed + read; + m_unprocessed.clear(); + } + if (!read.endsWith('\n')) { + m_unprocessed = read; + continue; + } + read.chop(1); // remove the newline from the output + + const QString line = QString::fromLatin1(read); + if (line.trimmed().isEmpty()) + continue; + + if (!line.startsWith(QLatin1Char('['))) { + m_description.append(line).append(QLatin1Char('\n')); + if (line.startsWith(QStringLiteral("Note:"))) { + auto testResult = new GTestResult(); + testResult->setResult(Result::MessageInternal); + testResult->setDescription(line); + m_futureInterface.reportResult(testResult); + m_description.clear(); + } else if (disabledTests.exactMatch(line)) { + auto testResult = new GTestResult(); + testResult->setResult(Result::MessageDisabledTests); + int disabled = disabledTests.cap(1).toInt(); + testResult->setDescription(tr("You have %n disabled test(s).", 0, disabled)); + testResult->setLine(disabled); // misuse line property to hold number of disabled + m_futureInterface.reportResult(testResult); + m_description.clear(); + } + continue; + } + + if (testEnds.exactMatch(line)) { + auto testResult = new GTestResult(m_currentTestName); + testResult->setTestCase(m_currentTestSet); + testResult->setResult(Result::MessageTestCaseEnd); + testResult->setDescription(tr("Test execution took %1").arg(testEnds.cap(2))); + m_futureInterface.reportResult(testResult); + m_currentTestName.clear(); + m_currentTestSet.clear(); + } else if (newTestStarts.exactMatch(line)) { + m_currentTestName = newTestStarts.cap(1); + auto testResult = new GTestResult(m_currentTestName); + testResult->setResult(Result::MessageTestCaseStart); + testResult->setDescription(tr("Executing test case %1").arg(m_currentTestName)); + m_futureInterface.reportResult(testResult); + } else if (newTestSetStarts.exactMatch(line)) { + m_currentTestSet = newTestSetStarts.cap(1); + auto testResult = new GTestResult(); + testResult->setResult(Result::MessageCurrentTest); + testResult->setDescription(tr("Entering test set %1").arg(m_currentTestSet)); + m_futureInterface.reportResult(testResult); + } else if (testSetSuccess.exactMatch(line)) { + auto testResult = new GTestResult(m_currentTestName); + testResult->setTestCase(m_currentTestSet); + testResult->setResult(Result::Pass); + m_futureInterface.reportResult(testResult); + testResult = new GTestResult(m_currentTestName); + testResult->setTestCase(m_currentTestSet); + testResult->setResult(Result::MessageInternal); + testResult->setDescription(tr("Execution took %1.").arg(testSetSuccess.cap(2))); + m_futureInterface.reportResult(testResult); + m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); + } else if (testSetFail.exactMatch(line)) { + auto testResult = new GTestResult(m_currentTestName); + testResult->setTestCase(m_currentTestSet); + testResult->setResult(Result::Fail); + m_description.chop(1); + testResult->setDescription(m_description); + int firstColon = m_description.indexOf(QLatin1Char(':')); + if (firstColon != -1) { + int secondColon = m_description.indexOf(QLatin1Char(':'), firstColon + 1); + const QString base = QFileInfo(m_testApplication->program()).absolutePath(); + QString file = constructSourceFilePath(base, m_description.left(firstColon), + m_testApplication->program()); + QString line = m_description.mid(firstColon + 1, secondColon - firstColon - 1); + testResult->setFileName(file); + testResult->setLine(line.toInt()); + } + m_futureInterface.reportResult(testResult); + m_description.clear(); + testResult = new GTestResult(m_currentTestName); + testResult->setTestCase(m_currentTestSet); + testResult->setResult(Result::MessageInternal); + testResult->setDescription(tr("Execution took %1.").arg(testSetFail.cap(2))); + m_futureInterface.reportResult(testResult); + m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); + } + } +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testoutputreader.h b/src/plugins/autotest/testoutputreader.h new file mode 100644 index 00000000000..9a50e605573 --- /dev/null +++ b/src/plugins/autotest/testoutputreader.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTXMLOUTPUTREADER_H +#define TESTXMLOUTPUTREADER_H + +#include "testresult.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QProcess; +QT_END_NAMESPACE + +namespace Autotest { +namespace Internal { + +class TestOutputReader : public QObject +{ + Q_OBJECT +public: + TestOutputReader(QFutureInterface futureInterface, + QProcess *testApplication); + +protected: + virtual void processOutput() = 0; + QFutureInterface m_futureInterface; + QProcess *m_testApplication; // not owned +}; + +class QtTestOutputReader : public TestOutputReader +{ +public: + QtTestOutputReader(QFutureInterface futureInterface, + QProcess *testApplication); + +protected: + void processOutput() override; + +private: + enum CDATAMode + { + None, + DataTag, + Description, + QtVersion, + QtBuild, + QTestVersion + }; + + CDATAMode m_cdataMode = None; + QString m_className; + QString m_testCase; + QString m_dataTag; + Result::Type m_result = Result::Invalid; + QString m_description; + QString m_file; + int m_lineNumber = 0; + QString m_duration; + QXmlStreamReader m_xmlReader; +}; + +class GTestOutputReader : public TestOutputReader +{ +public: + GTestOutputReader(QFutureInterface futureInterface, + QProcess *testApplication); + +protected: + void processOutput() override; + +private: + QString m_currentTestName; + QString m_currentTestSet; + QString m_description; + QByteArray m_unprocessed; +}; + + +} // namespace Internal +} // namespace Autotest + +#endif // TESTOUTPUTREADER_H diff --git a/src/plugins/autotest/testresult.cpp b/src/plugins/autotest/testresult.cpp new file mode 100644 index 00000000000..a7f3cc34638 --- /dev/null +++ b/src/plugins/autotest/testresult.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "testresult.h" + +namespace Autotest { +namespace Internal { + +FaultyTestResult::FaultyTestResult(Result::Type result, const QString &description) +{ + setResult(result); + setDescription(description); +} + +TestResult::TestResult() + : TestResult(QString()) +{ +} + +TestResult::TestResult(const QString &className) + : m_class(className) + , m_result(Result::Invalid) + , m_line(0) + , m_type(TestTypeQt) +{ +} + +Result::Type TestResult::resultFromString(const QString &resultString) +{ + if (resultString == QLatin1String("pass")) + return Result::Pass; + if (resultString == QLatin1String("fail")) + return Result::Fail; + if (resultString == QLatin1String("xfail")) + return Result::ExpectedFail; + if (resultString == QLatin1String("xpass")) + return Result::UnexpectedPass; + if (resultString == QLatin1String("skip")) + return Result::Skip; + if (resultString == QLatin1String("qdebug")) + return Result::MessageDebug; + if (resultString == QLatin1String("warn") || resultString == QLatin1String("qwarn")) + return Result::MessageWarn; + if (resultString == QLatin1String("qfatal")) + return Result::MessageFatal; + if (resultString == QLatin1String("bpass")) + return Result::BlacklistedPass; + if (resultString == QLatin1String("bfail")) + return Result::BlacklistedFail; + qDebug("Unexpected test result: %s", qPrintable(resultString)); + return Result::Invalid; +} + +Result::Type TestResult::toResultType(int rt) +{ + if (rt < Result::FIRST_TYPE || rt > Result::LAST_TYPE) + return Result::Invalid; + + return (Result::Type)rt; +} + +QString TestResult::resultToString(const Result::Type type) +{ + if (type >= Result::INTERNAL_MESSAGES_BEGIN && type <= Result::INTERNAL_MESSAGES_END) + return QString(); + + switch (type) { + case Result::Pass: + return QLatin1String("PASS"); + case Result::Fail: + return QLatin1String("FAIL"); + case Result::ExpectedFail: + return QLatin1String("XFAIL"); + case Result::UnexpectedPass: + return QLatin1String("XPASS"); + case Result::Skip: + return QLatin1String("SKIP"); + case Result::Benchmark: + return QLatin1String("BENCH"); + case Result::MessageDebug: + return QLatin1String("DEBUG"); + case Result::MessageWarn: + return QLatin1String("WARN"); + case Result::MessageFatal: + return QLatin1String("FATAL"); + case Result::BlacklistedPass: + return QLatin1String("BPASS"); + case Result::BlacklistedFail: + return QLatin1String("BFAIL"); + default: + return QLatin1String("UNKNOWN"); + } +} + +QColor TestResult::colorForType(const Result::Type type) +{ + if (type >= Result::INTERNAL_MESSAGES_BEGIN && type <= Result::INTERNAL_MESSAGES_END) + return QColor("transparent"); + + switch (type) { + case Result::Pass: + return QColor("#009900"); + case Result::Fail: + return QColor("#a00000"); + case Result::ExpectedFail: + return QColor("#00ff00"); + case Result::UnexpectedPass: + return QColor("#ff0000"); + case Result::Skip: + return QColor("#787878"); + case Result::BlacklistedPass: + return QColor(0, 0, 0); + case Result::BlacklistedFail: + return QColor(0, 0, 0); + case Result::MessageDebug: + return QColor("#329696"); + case Result::MessageWarn: + return QColor("#d0bb00"); + case Result::MessageFatal: + return QColor("#640000"); + default: + return QColor("#000000"); + } +} + +bool operator==(const TestResult &t1, const TestResult &t2) +{ + return t1.className() == t2.className() + && t1.testCase() == t2.testCase() + && t1.dataTag() == t2.dataTag() + && t1.result() == t2.result(); +} + +QTestResult::QTestResult(const QString &className) + : TestResult(className) +{ +} + +GTestResult::GTestResult(const QString &className) + : TestResult(className) +{ + setTestType(TestTypeGTest); +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testresult.h b/src/plugins/autotest/testresult.h new file mode 100644 index 00000000000..91e380c9c35 --- /dev/null +++ b/src/plugins/autotest/testresult.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTRESULT_H +#define TESTRESULT_H + +#include "autotestconstants.h" + +#include +#include +#include + +namespace Autotest { +namespace Internal { + +namespace Result{ +enum Type { + Pass, FIRST_TYPE = Pass, + Fail, + ExpectedFail, + UnexpectedPass, + Skip, + BlacklistedPass, + BlacklistedFail, + Benchmark, + MessageDebug, + MessageWarn, + MessageFatal, + + MessageInternal, INTERNAL_MESSAGES_BEGIN = MessageInternal, + MessageDisabledTests, + MessageTestCaseStart, + MessageTestCaseSuccess, + MessageTestCaseWarn, + MessageTestCaseFail, + MessageTestCaseEnd, + MessageCurrentTest, INTERNAL_MESSAGES_END = MessageCurrentTest, + + Invalid, + LAST_TYPE = Invalid +}; +} + +class TestResult +{ +public: + TestResult(); + TestResult(const QString &className); + + QString className() const { return m_class; } + QString testCase() const { return m_case; } + QString dataTag() const { return m_dataTag; } + Result::Type result() const { return m_result; } + QString description() const { return m_description; } + QString fileName() const { return m_file; } + int line() const { return m_line; } + TestType type() const { return m_type; } + + void setDescription(const QString &description) { m_description = description; } + void setFileName(const QString &fileName) { m_file = fileName; } + void setLine(int line) { m_line = line; } + void setResult(Result::Type type) { m_result = type; } + void setTestCase(const QString &testCase) { m_case = testCase; } + void setDataTag(const QString &dataTag) { m_dataTag = dataTag; } + void setTestType(TestType type) { m_type = type; } + + static Result::Type resultFromString(const QString &resultString); + static Result::Type toResultType(int rt); + static QString resultToString(const Result::Type type); + static QColor colorForType(const Result::Type type); + +private: + QString m_class; + QString m_case; + QString m_dataTag; + Result::Type m_result; + QString m_description; + QString m_file; + int m_line; + TestType m_type; + // environment? +}; + +class FaultyTestResult : public TestResult +{ +public: + FaultyTestResult(Result::Type result, const QString &description); +}; + +class QTestResult : public TestResult +{ +public: + QTestResult(const QString &className = QString()); +}; + +class GTestResult : public TestResult +{ +public: + GTestResult(const QString &className = QString()); +}; + +bool operator==(const TestResult &t1, const TestResult &t2); + +} // namespace Internal +} // namespace Autotest + +Q_DECLARE_METATYPE(Autotest::Internal::TestResult) +Q_DECLARE_METATYPE(Autotest::Internal::Result::Type) + +#endif // TESTRESULT_H diff --git a/src/plugins/autotest/testresultdelegate.cpp b/src/plugins/autotest/testresultdelegate.cpp new file mode 100644 index 00000000000..305b6aaff82 --- /dev/null +++ b/src/plugins/autotest/testresultdelegate.cpp @@ -0,0 +1,254 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "autotestplugin.h" +#include "testresultdelegate.h" +#include "testresultmodel.h" +#include "testsettings.h" + +#include +#include +#include +#include + +namespace Autotest { +namespace Internal { + +const static int outputLimit = 100000; + +TestResultDelegate::TestResultDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + +QString TestResultDelegate::outputString(const TestResult &testResult, bool selected) +{ + const QString desc = testResult.description(); + QString output; + switch (testResult.result()) { + case Result::Pass: + case Result::Fail: + case Result::ExpectedFail: + case Result::UnexpectedPass: + case Result::BlacklistedFail: + case Result::BlacklistedPass: + if (testResult.type() == TestTypeQt) + output = testResult.className() + QLatin1String("::") + testResult.testCase(); + else // TestTypeGTest + output = testResult.testCase(); + if (!testResult.dataTag().isEmpty()) + output.append(QString::fromLatin1(" (%1)").arg(testResult.dataTag())); + if (selected && !desc.isEmpty()) { + output.append(QLatin1Char('\n')).append(desc); + } + break; + case Result::Benchmark: + output = testResult.className() + QLatin1String("::") + testResult.testCase(); + if (!testResult.dataTag().isEmpty()) + output.append(QString::fromLatin1(" (%1)").arg(testResult.dataTag())); + if (!desc.isEmpty()) { + int breakPos = desc.indexOf(QLatin1Char('(')); + output.append(QLatin1String(": ")).append(desc.left(breakPos)); + if (selected) + output.append(QLatin1Char('\n')).append(desc.mid(breakPos)); + } + break; + default: + output = desc; + if (!selected) + output = output.split(QLatin1Char('\n')).first(); + } + return output; +} + +void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + // make sure we paint the complete delegate instead of keeping an offset + opt.rect.adjust(-opt.rect.x(), 0, 0, 0); + painter->save(); + + QFontMetrics fm(opt.font); + QColor foreground; + + const QAbstractItemView *view = qobject_cast(opt.widget); + const bool selected = view->selectionModel()->currentIndex() == index; + + if (selected) { + painter->setBrush(opt.palette.highlight().color()); + foreground = opt.palette.highlightedText().color(); + } else { + painter->setBrush(opt.palette.background().color()); + foreground = opt.palette.text().color(); + } + painter->setPen(Qt::NoPen); + painter->drawRect(opt.rect); + painter->setPen(foreground); + + TestResultFilterModel *resultFilterModel = static_cast(view->model()); + LayoutPositions positions(opt, resultFilterModel); + const TestResult &testResult = resultFilterModel->testResult(index); + + // draw the indicator by ourself as we paint across it with the delegate + QStyleOptionViewItemV4 indicatorOpt = option; + indicatorOpt.rect = QRect(0, opt.rect.y(), positions.indentation(), opt.rect.height()); + opt.widget->style()->drawPrimitive(QStyle::PE_IndicatorBranch, &indicatorOpt, painter); + + QIcon icon = index.data(Qt::DecorationRole).value(); + if (!icon.isNull()) + painter->drawPixmap(positions.left(), positions.top(), + icon.pixmap(positions.iconSize(), positions.iconSize())); + + QString typeStr = TestResult::resultToString(testResult.result()); + if (selected) { + painter->drawText(positions.typeAreaLeft(), positions.top() + fm.ascent(), typeStr); + } else { + QPen tmp = painter->pen(); + painter->setPen(TestResult::colorForType(testResult.result())); + painter->drawText(positions.typeAreaLeft(), positions.top() + fm.ascent(), typeStr); + painter->setPen(tmp); + } + + QString output = outputString(testResult, selected); + + if (selected) { + output.replace(QLatin1Char('\n'), QChar::LineSeparator); + + if (AutotestPlugin::instance()->settings()->limitResultOutput + && output.length() > outputLimit) + output = output.left(outputLimit).append(QLatin1String("...")); + + recalculateTextLayout(index, output, painter->font(), positions.textAreaWidth()); + + m_lastCalculatedLayout.draw(painter, QPoint(positions.textAreaLeft(), positions.top())); + } else { + painter->setClipRect(positions.textArea()); + // cut output before generating elided text as this takes quite long for exhaustive output + painter->drawText(positions.textAreaLeft(), positions.top() + fm.ascent(), + fm.elidedText(output.left(2000), Qt::ElideRight, positions.textAreaWidth())); + } + + QString file = testResult.fileName(); + const int pos = file.lastIndexOf(QLatin1Char('/')); + if (pos != -1) + file = file.mid(pos + 1); + + painter->setClipRect(positions.fileArea()); + painter->drawText(positions.fileAreaLeft(), positions.top() + fm.ascent(), file); + + + if (testResult.line()) { + QString line = QString::number(testResult.line()); + painter->setClipRect(positions.lineArea()); + painter->drawText(positions.lineAreaLeft(), positions.top() + fm.ascent(), line); + } + + painter->setClipRect(opt.rect); + painter->setPen(opt.palette.midlight().color()); + painter->drawLine(0, opt.rect.bottom(), opt.rect.right(), opt.rect.bottom()); + painter->restore(); +} + +QSize TestResultDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItemV4 opt = option; + // make sure opt.rect is initialized correctly - otherwise we might get a width of 0 + opt.initFrom(opt.widget); + initStyleOption(&opt, index); + + const QAbstractItemView *view = qobject_cast(opt.widget); + const bool selected = view->selectionModel()->currentIndex() == index; + + QFontMetrics fm(opt.font); + int fontHeight = fm.height(); + TestResultFilterModel *resultFilterModel = static_cast(view->model()); + LayoutPositions positions(opt, resultFilterModel); + QSize s; + s.setWidth(opt.rect.width()); + + if (selected) { + const TestResult &testResult = resultFilterModel->testResult(index); + + QString output = outputString(testResult, selected); + output.replace(QLatin1Char('\n'), QChar::LineSeparator); + + if (AutotestPlugin::instance()->settings()->limitResultOutput + && output.length() > outputLimit) + output = output.left(outputLimit).append(QLatin1String("...")); + + recalculateTextLayout(index, output, opt.font, positions.textAreaWidth()); + + s.setHeight(m_lastCalculatedHeight + 3); + } else { + s.setHeight(fontHeight + 3); + } + + if (s.height() < positions.minimumHeight()) + s.setHeight(positions.minimumHeight()); + + return s; +} + +void TestResultDelegate::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + emit sizeHintChanged(current); + emit sizeHintChanged(previous); +} + +void TestResultDelegate::recalculateTextLayout(const QModelIndex &index, const QString &output, + const QFont &font, int width) const +{ + if (m_lastProcessedIndex == index && m_lastProcessedFont == font) + return; + + const QFontMetrics fm(font); + const int leading = fm.leading(); + const int fontHeight = fm.height(); + + m_lastProcessedIndex = index; + m_lastProcessedFont = font; + m_lastCalculatedHeight = 0; + m_lastCalculatedLayout.clearLayout(); + m_lastCalculatedLayout.setText(output); + m_lastCalculatedLayout.setFont(font); + QTextOption txtOption; + txtOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + m_lastCalculatedLayout.setTextOption(txtOption); + m_lastCalculatedLayout.beginLayout(); + while (true) { + QTextLine line = m_lastCalculatedLayout.createLine(); + if (!line.isValid()) + break; + line.setLineWidth(width); + m_lastCalculatedHeight += leading; + line.setPosition(QPoint(0, m_lastCalculatedHeight)); + m_lastCalculatedHeight += fontHeight; + } + m_lastCalculatedLayout.endLayout(); +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testresultdelegate.h b/src/plugins/autotest/testresultdelegate.h new file mode 100644 index 00000000000..ceaecdc8c62 --- /dev/null +++ b/src/plugins/autotest/testresultdelegate.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTRESULTDELEGATE_H +#define TESTRESULTDELEGATE_H + +#include "testresultmodel.h" + +#include +#include + +namespace Autotest { +namespace Internal { + +class TestResultDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit TestResultDelegate(QObject *parent = 0); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + static QString outputString(const TestResult &testResult, bool selected); + +public slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + +private: + void recalculateTextLayout(const QModelIndex &index, const QString &output, + const QFont &font, int width) const; + + mutable QModelIndex m_lastProcessedIndex; + mutable QFont m_lastProcessedFont; + mutable QTextLayout m_lastCalculatedLayout; + mutable int m_lastCalculatedHeight; + + class LayoutPositions + { + public: + LayoutPositions(QStyleOptionViewItemV4 &options, TestResultFilterModel *filterModel) + : m_totalWidth(options.rect.width()), + m_top(options.rect.top()), + m_bottom(options.rect.bottom()) + { + TestResultModel *srcModel = static_cast(filterModel->sourceModel()); + m_maxFileLength = srcModel->maxWidthOfFileName(options.font); + m_maxLineLength = srcModel->maxWidthOfLineNumber(options.font); + m_realFileLength = m_maxFileLength; + m_typeAreaWidth = QFontMetrics(options.font).width(QLatin1String("XXXXXXXX")); + m_indentation = options.widget ? options.widget->style()->pixelMetric( + QStyle::PM_TreeViewIndentation, &options) : 0; + m_level = srcModel->hasChildren(filterModel->mapToSource(options.index)) ? 1 : 2; + int flexibleArea = lineAreaLeft() - textAreaLeft() - ITEM_SPACING; + if (m_maxFileLength > flexibleArea / 2) + m_realFileLength = flexibleArea / 2; + m_fontHeight = QFontMetrics(options.font).height(); + } + + int top() const { return m_top + ITEM_MARGIN; } + int left() const { return ITEM_MARGIN + m_indentation * m_level; } + int right() const { return m_totalWidth - ITEM_MARGIN; } + int bottom() const { return m_bottom; } + int minimumHeight() const { return ICON_SIZE + 2 * ITEM_MARGIN; } + + int iconSize() const { return ICON_SIZE; } + int fontHeight() const { return m_fontHeight; } + int typeAreaLeft() const { return left() + ICON_SIZE + ITEM_SPACING; } + int typeAreaWidth() const { return m_typeAreaWidth; } + int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ITEM_SPACING + + (1 - m_level) * m_indentation; } + int textAreaWidth() const { return fileAreaLeft() - ITEM_SPACING - textAreaLeft(); } + int fileAreaLeft() const { return lineAreaLeft() - ITEM_SPACING - m_realFileLength; } + int lineAreaLeft() const { return right() - m_maxLineLength; } + int indentation() const { return m_indentation; } + + QRect typeArea() const { return QRect(typeAreaLeft(), top(), + typeAreaWidth(), m_fontHeight); } + QRect textArea() const { return QRect(textAreaLeft(), top(), + textAreaWidth(), m_fontHeight); } + QRect fileArea() const { return QRect(fileAreaLeft(), top(), + m_realFileLength + ITEM_SPACING, m_fontHeight); } + + QRect lineArea() const { return QRect(lineAreaLeft(), top(), + m_maxLineLength, m_fontHeight); } + + private: + int m_totalWidth; + int m_maxFileLength; + int m_maxLineLength; + int m_realFileLength; + int m_top; + int m_bottom; + int m_fontHeight; + int m_typeAreaWidth; + int m_level; + int m_indentation; + + static const int ICON_SIZE = 16; + static const int ITEM_MARGIN = 2; + static const int ITEM_SPACING = 4; + + }; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTRESULTDELEGATE_H diff --git a/src/plugins/autotest/testresultmodel.cpp b/src/plugins/autotest/testresultmodel.cpp new file mode 100644 index 00000000000..835850fe733 --- /dev/null +++ b/src/plugins/autotest/testresultmodel.cpp @@ -0,0 +1,337 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "testresultmodel.h" + +#include +#include + +namespace Autotest { +namespace Internal { + +/********************************* TestResultItem ******************************************/ + +TestResultItem::TestResultItem(TestResult *testResult) + : m_testResult(testResult) +{ +} + +TestResultItem::~TestResultItem() +{ + delete m_testResult; +} + +static QIcon testResultIcon(Result::Type result) { + static QIcon icons[11] = { + QIcon(QLatin1String(":/images/pass.png")), + QIcon(QLatin1String(":/images/fail.png")), + QIcon(QLatin1String(":/images/xfail.png")), + QIcon(QLatin1String(":/images/xpass.png")), + QIcon(QLatin1String(":/images/skip.png")), + QIcon(QLatin1String(":/images/blacklisted_pass.png")), + QIcon(QLatin1String(":/images/blacklisted_fail.png")), + QIcon(QLatin1String(":/images/benchmark.png")), + QIcon(QLatin1String(":/images/debug.png")), + QIcon(QLatin1String(":/images/warn.png")), + QIcon(QLatin1String(":/images/fatal.png")), + }; // provide an icon for unknown?? + + if (result < 0 || result >= Result::MessageInternal) { + switch (result) { + case Result::MessageTestCaseSuccess: + return icons[Result::Pass]; + case Result::MessageTestCaseFail: + return icons[Result::Fail]; + case Result::MessageTestCaseWarn: + return icons[Result::MessageWarn]; + default: + return QIcon(); + } + } + return icons[result]; +} + +QVariant TestResultItem::data(int column, int role) const +{ + if (role == Qt::DecorationRole) + return m_testResult ? testResultIcon(m_testResult->result()) : QVariant(); + + return Utils::TreeItem::data(column, role); +} + +void TestResultItem::updateDescription(const QString &description) +{ + QTC_ASSERT(m_testResult, return); + + m_testResult->setDescription(description); +} + +void TestResultItem::updateResult() +{ + if (m_testResult->result() != Result::MessageTestCaseStart) + return; + + Result::Type newResult = Result::MessageTestCaseSuccess; + foreach (Utils::TreeItem *child, children()) { + const TestResult *current = static_cast(child)->testResult(); + if (current) { + switch (current->result()) { + case Result::Fail: + case Result::MessageFatal: + case Result::UnexpectedPass: + m_testResult->setResult(Result::MessageTestCaseFail); + return; + case Result::ExpectedFail: + case Result::MessageWarn: + case Result::Skip: + case Result::BlacklistedFail: + case Result::BlacklistedPass: + newResult = Result::MessageTestCaseWarn; + break; + default: {} + } + } + } + m_testResult->setResult(newResult); +} + +/********************************* TestResultModel *****************************************/ + +TestResultModel::TestResultModel(QObject *parent) + : Utils::TreeModel(parent), + m_widthOfLineNumber(0), + m_maxWidthOfFileName(0), + m_disabled(0) +{ +} + +QVariant TestResultModel::data(const QModelIndex &idx, int role) const +{ + if (!idx.isValid()) + return QVariant(); + + if (role == Qt::DecorationRole) + return itemForIndex(idx)->data(0, Qt::DecorationRole); + + return QVariant(); +} + +void TestResultModel::addTestResult(TestResult *testResult, bool autoExpand) +{ + const bool isCurrentTestMssg = testResult->result() == Result::MessageCurrentTest; + + QVector topLevelItems = rootItem()->children(); + int lastRow = topLevelItems.size() - 1; + // we'll add the new item, so raising it's counter + if (!isCurrentTestMssg) { + int count = m_testResultCount.value(testResult->result(), 0); + if (testResult->result() == Result::MessageDisabledTests) + m_disabled += testResult->line(); + m_testResultCount.insert(testResult->result(), ++count); + } else { + // MessageCurrentTest should always be the last top level item + if (lastRow >= 0) { + TestResultItem *current = static_cast(topLevelItems.at(lastRow)); + const TestResult *result = current->testResult(); + if (result && result->result() == Result::MessageCurrentTest) { + current->updateDescription(testResult->description()); + emit dataChanged(current->index(), current->index()); + delete testResult; + return; + } + } + + rootItem()->appendChild(new TestResultItem(testResult)); + return; + } + + TestResultItem *newItem = new TestResultItem(testResult); + // FIXME this might be totally wrong... we need some more unique information! + if (!testResult->className().isEmpty()) { + for (int row = lastRow; row >= 0; --row) { + TestResultItem *current = static_cast(topLevelItems.at(row)); + const TestResult *result = current->testResult(); + if (result && result->className() == testResult->className()) { + current->appendChild(newItem); + if (autoExpand) + current->expand(); + if (testResult->result() == Result::MessageTestCaseEnd) { + current->updateResult(); + emit dataChanged(current->index(), current->index()); + } + return; + } + } + } + // if we have a MessageCurrentTest present, add the new top level item before it + if (lastRow >= 0) { + TestResultItem *current = static_cast(topLevelItems.at(lastRow)); + const TestResult *result = current->testResult(); + if (result && result->result() == Result::MessageCurrentTest) { + rootItem()->insertChild(current->index().row(), newItem); + return; + } + } + + rootItem()->appendChild(newItem); +} + +void TestResultModel::removeCurrentTestMessage() +{ + QVector topLevelItems = rootItem()->children(); + for (int row = topLevelItems.size() - 1; row >= 0; --row) { + TestResultItem *current = static_cast(topLevelItems.at(row)); + if (current->testResult()->result() == Result::MessageCurrentTest) { + delete takeItem(current); + break; + } + } +} + +void TestResultModel::clearTestResults() +{ + clear(); + m_testResultCount.clear(); + m_disabled = 0; + m_processedIndices.clear(); + m_maxWidthOfFileName = 0; + m_widthOfLineNumber = 0; +} + +TestResult TestResultModel::testResult(const QModelIndex &idx) +{ + if (idx.isValid()) + return *(static_cast(itemForIndex(idx))->testResult()); + + return TestResult(); +} + +int TestResultModel::maxWidthOfFileName(const QFont &font) +{ + if (font != m_measurementFont) { + m_processedIndices.clear(); + m_maxWidthOfFileName = 0; + m_measurementFont = font; + } + + const QFontMetrics fm(font); + const QVector &topLevelItems = rootItem()->children(); + const int count = topLevelItems.size(); + for (int row = 0; row < count; ++row) { + int processed = row < m_processedIndices.size() ? m_processedIndices.at(row) : 0; + const QVector &children = topLevelItems.at(row)->children(); + const int itemCount = children.size(); + if (processed < itemCount) { + for (int childRow = processed; childRow < itemCount; ++childRow) { + const TestResultItem *item = static_cast(children.at(childRow)); + if (const TestResult *result = item->testResult()) { + QString fileName = result->fileName(); + const int pos = fileName.lastIndexOf(QLatin1Char('/')); + if (pos != -1) + fileName = fileName.mid(pos + 1); + m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(fileName)); + } + } + if (row < m_processedIndices.size()) + m_processedIndices.replace(row, itemCount); + else + m_processedIndices.insert(row, itemCount); + } + } + return m_maxWidthOfFileName; +} + +int TestResultModel::maxWidthOfLineNumber(const QFont &font) +{ + if (m_widthOfLineNumber == 0 || font != m_measurementFont) { + QFontMetrics fm(font); + m_measurementFont = font; + m_widthOfLineNumber = fm.width(QLatin1String("88888")); + } + return m_widthOfLineNumber; +} + +/********************************** Filter Model **********************************/ + +TestResultFilterModel::TestResultFilterModel(TestResultModel *sourceModel, QObject *parent) + : QSortFilterProxyModel(parent), + m_sourceModel(sourceModel) +{ + setSourceModel(sourceModel); + enableAllResultTypes(); +} + +void TestResultFilterModel::enableAllResultTypes() +{ + m_enabled << Result::Pass << Result::Fail << Result::ExpectedFail + << Result::UnexpectedPass << Result::Skip << Result::MessageDebug + << Result::MessageWarn << Result::MessageInternal + << Result::MessageFatal << Result::Invalid << Result::BlacklistedPass + << Result::BlacklistedFail << Result::Benchmark + << Result::MessageCurrentTest << Result::MessageTestCaseStart + << Result::MessageTestCaseSuccess << Result::MessageTestCaseWarn + << Result::MessageTestCaseFail << Result::MessageTestCaseEnd; + invalidateFilter(); +} + +void TestResultFilterModel::toggleTestResultType(Result::Type type) +{ + if (m_enabled.contains(type)) { + m_enabled.remove(type); + if (type == Result::MessageInternal) + m_enabled.remove(Result::MessageTestCaseEnd); + } else { + m_enabled.insert(type); + if (type == Result::MessageInternal) + m_enabled.insert(Result::MessageTestCaseEnd); + } + invalidateFilter(); +} + +void TestResultFilterModel::clearTestResults() +{ + m_sourceModel->clearTestResults(); +} + +bool TestResultFilterModel::hasResults() +{ + return rowCount(QModelIndex()); +} + +TestResult TestResultFilterModel::testResult(const QModelIndex &index) const +{ + return m_sourceModel->testResult(mapToSource(index)); +} + +bool TestResultFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex index = m_sourceModel->index(sourceRow, 0, sourceParent); + if (!index.isValid()) + return false; + return m_enabled.contains(m_sourceModel->testResult(index).result()); +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testresultmodel.h b/src/plugins/autotest/testresultmodel.h new file mode 100644 index 00000000000..34f117e5381 --- /dev/null +++ b/src/plugins/autotest/testresultmodel.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTRESULTMODEL_H +#define TESTRESULTMODEL_H + +#include "testresult.h" + +#include +#include +#include +#include + +#include + +namespace Autotest { +namespace Internal { + +class TestResultItem : public Utils::TreeItem +{ +public: + explicit TestResultItem(TestResult *testResult); + ~TestResultItem(); + QVariant data(int column, int role) const; + const TestResult *testResult() const { return m_testResult; } + void updateDescription(const QString &description); + void updateResult(); + +private: + TestResult *m_testResult; +}; + +class TestResultModel : public Utils::TreeModel +{ +public: + explicit TestResultModel(QObject *parent = 0); + QVariant data(const QModelIndex &idx, int role) const; + + void addTestResult(TestResult *testResult, bool autoExpand = false); + void removeCurrentTestMessage(); + void clearTestResults(); + + TestResult testResult(const QModelIndex &idx); + + int maxWidthOfFileName(const QFont &font); + int maxWidthOfLineNumber(const QFont &font); + + int resultTypeCount(Result::Type type) const { return m_testResultCount.value(type, 0); } + int disabledTests() const { return m_disabled; } + +private: + QMap m_testResultCount; + int m_widthOfLineNumber; + int m_maxWidthOfFileName; + int m_disabled; + QList m_processedIndices; + QFont m_measurementFont; +}; + +class TestResultFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + TestResultFilterModel(TestResultModel *sourceModel, QObject *parent = 0); + + void enableAllResultTypes(); + void toggleTestResultType(Result::Type type); + void clearTestResults(); + bool hasResults(); + TestResult testResult(const QModelIndex &index) const; + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + +private: + TestResultModel *m_sourceModel; + QSet m_enabled; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTRESULTMODEL_H diff --git a/src/plugins/autotest/testresultspane.cpp b/src/plugins/autotest/testresultspane.cpp new file mode 100644 index 00000000000..e46c9e7809f --- /dev/null +++ b/src/plugins/autotest/testresultspane.cpp @@ -0,0 +1,581 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "autotestplugin.h" +#include "autotesticons.h" +#include "testresultspane.h" +#include "testresultmodel.h" +#include "testresultdelegate.h" +#include "testrunner.h" +#include "testsettings.h" +#include "testtreemodel.h" + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Autotest { +namespace Internal { + +ResultsTreeView::ResultsTreeView(QWidget *parent) + : Utils::TreeView(parent) +{ + setAttribute(Qt::WA_MacShowFocusRect, false); +} + +void ResultsTreeView::keyPressEvent(QKeyEvent *event) +{ + if (event->matches(QKeySequence::Copy)) { + emit copyShortcutTriggered(); + event->accept(); + } + TreeView::keyPressEvent(event); +} + +TestResultsPane::TestResultsPane(QObject *parent) : + Core::IOutputPane(parent), + m_context(new Core::IContext(this)), + m_wasVisibleBefore(false), + m_autoScroll(false), + m_testRunning(false) +{ + m_outputWidget = new QWidget; + QVBoxLayout *outputLayout = new QVBoxLayout; + outputLayout->setMargin(0); + outputLayout->setSpacing(0); + m_outputWidget->setLayout(outputLayout); + + QPalette pal; + pal.setColor(QPalette::Window, + Utils::creatorTheme()->color(Utils::Theme::InfoBarBackground)); + pal.setColor(QPalette::WindowText, + Utils::creatorTheme()->color(Utils::Theme::InfoBarText)); + m_summaryWidget = new QFrame; + m_summaryWidget->setPalette(pal); + m_summaryWidget->setAutoFillBackground(true); + QHBoxLayout *layout = new QHBoxLayout; + layout->setMargin(6); + m_summaryWidget->setLayout(layout); + m_summaryLabel = new QLabel; + m_summaryLabel->setPalette(pal); + layout->addWidget(m_summaryLabel); + m_summaryWidget->setVisible(false); + + outputLayout->addWidget(m_summaryWidget); + + m_treeView = new ResultsTreeView(m_outputWidget); + m_treeView->setHeaderHidden(true); + m_treeView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); + m_model = new TestResultModel(this); + m_filterModel = new TestResultFilterModel(m_model, this); + m_filterModel->setDynamicSortFilter(true); + m_treeView->setModel(m_filterModel); + TestResultDelegate *trd = new TestResultDelegate(this); + m_treeView->setItemDelegate(trd); + + outputLayout->addWidget(m_treeView); + + createToolButtons(); + + connect(m_treeView, &Utils::TreeView::activated, this, &TestResultsPane::onItemActivated); + connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged, + trd, &TestResultDelegate::currentChanged); + connect(m_treeView, &Utils::TreeView::customContextMenuRequested, + this, &TestResultsPane::onCustomContextMenuRequested); + connect(m_treeView, &ResultsTreeView::copyShortcutTriggered, [this] () { + onCopyItemTriggered(m_treeView->currentIndex()); + }); + connect(m_model, &TestResultModel::requestExpansion, [this] (QModelIndex idx) { + m_treeView->expand(m_filterModel->mapFromSource(idx)); + }); + connect(TestRunner::instance(), &TestRunner::testRunStarted, + this, &TestResultsPane::onTestRunStarted); + connect(TestRunner::instance(), &TestRunner::testRunFinished, + this, &TestResultsPane::onTestRunFinished); + connect(TestRunner::instance(), &TestRunner::testResultReady, + this, &TestResultsPane::addTestResult); +} + +void TestResultsPane::createToolButtons() +{ + m_expandCollapse = new QToolButton(m_treeView); + m_expandCollapse->setIcon(Core::Icons::EXPAND.icon()); + m_expandCollapse->setToolTip(tr("Expand All")); + m_expandCollapse->setCheckable(true); + m_expandCollapse->setChecked(false); + connect(m_expandCollapse, &QToolButton::clicked, [this] (bool checked) { + if (checked) + m_treeView->expandAll(); + else + m_treeView->collapseAll(); + }); + + m_runAll = new QToolButton(m_treeView); + m_runAll->setIcon(ProjectExplorer::Icons::RUN_SMALL.icon()); + m_runAll->setToolTip(tr("Run All Tests")); + m_runAll->setEnabled(false); + connect(m_runAll, &QToolButton::clicked, this, &TestResultsPane::onRunAllTriggered); + + m_runSelected = new QToolButton(m_treeView); + Utils::Icon runSelectedIcon = ProjectExplorer::Icons::RUN_SMALL; + foreach (const Utils::IconMaskAndColor &maskAndColor, Icons::RUN_SELECTED_OVERLAY) + runSelectedIcon.append(maskAndColor); + m_runSelected->setIcon(runSelectedIcon.icon()); + m_runSelected->setToolTip(tr("Run Selected Tests")); + m_runSelected->setEnabled(false); + connect(m_runSelected, &QToolButton::clicked, this, &TestResultsPane::onRunSelectedTriggered); + + m_stopTestRun = new QToolButton(m_treeView); + m_stopTestRun->setIcon(ProjectExplorer::Icons::STOP_SMALL.icon()); + m_stopTestRun->setToolTip(tr("Stop Test Run")); + m_stopTestRun->setEnabled(false); + connect(m_stopTestRun, &QToolButton::clicked, TestRunner::instance(), &TestRunner::requestStopTestRun); + + m_filterButton = new QToolButton(m_treeView); + m_filterButton->setIcon(Core::Icons::FILTER.icon()); + m_filterButton->setToolTip(tr("Filter Test Results")); + m_filterButton->setProperty("noArrow", true); + m_filterButton->setAutoRaise(true); + m_filterButton->setPopupMode(QToolButton::InstantPopup); + m_filterMenu = new QMenu(m_filterButton); + initializeFilterMenu(); + connect(m_filterMenu, &QMenu::triggered, this, &TestResultsPane::filterMenuTriggered); + m_filterButton->setMenu(m_filterMenu); +} + +static TestResultsPane *m_instance = 0; + +TestResultsPane *TestResultsPane::instance() +{ + if (!m_instance) + m_instance = new TestResultsPane; + return m_instance; +} + +TestResultsPane::~TestResultsPane() +{ + delete m_treeView; + m_instance = 0; +} + +void TestResultsPane::addTestResult(TestResult *result) +{ + const QScrollBar *scrollBar = m_treeView->verticalScrollBar(); + m_atEnd = scrollBar ? scrollBar->value() == scrollBar->maximum() : true; + + m_model->addTestResult(result, m_expandCollapse->isChecked()); + if (!m_treeView->isVisible()) + popup(Core::IOutputPane::NoModeSwitch); + flash(); + navigateStateChanged(); +} + +QWidget *TestResultsPane::outputWidget(QWidget *parent) +{ + if (m_outputWidget) { + m_outputWidget->setParent(parent); + } else { + qDebug() << "This should not happen..."; + } + return m_outputWidget; +} + +QList TestResultsPane::toolBarWidgets() const +{ + return QList() << m_expandCollapse << m_runAll << m_runSelected << m_stopTestRun + << m_filterButton; +} + +QString TestResultsPane::displayName() const +{ + return tr("Test Results"); +} + +int TestResultsPane::priorityInStatusBar() const +{ + return -666; +} + +void TestResultsPane::clearContents() +{ + m_filterModel->clearTestResults(); + navigateStateChanged(); + m_summaryWidget->setVisible(false); + m_autoScroll = AutotestPlugin::instance()->settings()->autoScroll; + connect(m_treeView->verticalScrollBar(), &QScrollBar::rangeChanged, + this, &TestResultsPane::onScrollBarRangeChanged, Qt::UniqueConnection); +} + +void TestResultsPane::visibilityChanged(bool visible) +{ + if (visible == m_wasVisibleBefore) + return; + if (visible) { + connect(TestTreeModel::instance(), &TestTreeModel::testTreeModelChanged, + this, &TestResultsPane::onTestTreeModelChanged); + // make sure run/run all are in correct state + onTestTreeModelChanged(); + TestTreeModel::instance()->enableParsing(); + } else { + disconnect(TestTreeModel::instance(), &TestTreeModel::testTreeModelChanged, + this, &TestResultsPane::onTestTreeModelChanged); + TestTreeModel::instance()->disableParsing(); + } + m_wasVisibleBefore = visible; +} + +void TestResultsPane::setFocus() +{ +} + +bool TestResultsPane::hasFocus() const +{ + return m_treeView->hasFocus(); +} + +bool TestResultsPane::canFocus() const +{ + return true; +} + +bool TestResultsPane::canNavigate() const +{ + return true; +} + +bool TestResultsPane::canNext() const +{ + return m_filterModel->hasResults(); +} + +bool TestResultsPane::canPrevious() const +{ + return m_filterModel->hasResults(); +} + +void TestResultsPane::goToNext() +{ + if (!canNext()) + return; + + const QModelIndex currentIndex = m_treeView->currentIndex(); + QModelIndex nextCurrentIndex; + + if (currentIndex.isValid()) { + // try to set next to first child or next sibling + if (m_filterModel->rowCount(currentIndex)) { + nextCurrentIndex = currentIndex.child(0, 0); + } else { + nextCurrentIndex = currentIndex.sibling(currentIndex.row() + 1, 0); + // if it had no sibling check siblings of parent (and grandparents if necessary) + if (!nextCurrentIndex.isValid()) { + + QModelIndex parent = currentIndex.parent(); + do { + if (!parent.isValid()) + break; + nextCurrentIndex = parent.sibling(parent.row() + 1, 0); + parent = parent.parent(); + } while (!nextCurrentIndex.isValid()); + } + } + } + + // if we have no current or could not find a next one, use the first item of the whole tree + if (!nextCurrentIndex.isValid()) { + Utils::TreeItem *rootItem = m_model->itemForIndex(QModelIndex()); + // if the tree does not contain any item - don't do anything + if (!rootItem || !rootItem->childCount()) + return; + + nextCurrentIndex = m_filterModel->mapFromSource(m_model->indexForItem(rootItem->child(0))); + } + + m_treeView->setCurrentIndex(nextCurrentIndex); + onItemActivated(nextCurrentIndex); +} + +void TestResultsPane::goToPrev() +{ + if (!canPrevious()) + return; + + const QModelIndex currentIndex = m_treeView->currentIndex(); + QModelIndex nextCurrentIndex; + + if (currentIndex.isValid()) { + // try to set next to prior sibling or parent + if (currentIndex.row() > 0) { + nextCurrentIndex = currentIndex.sibling(currentIndex.row() - 1, 0); + // if the sibling has children, use the last one + while (int rowCount = m_filterModel->rowCount(nextCurrentIndex)) + nextCurrentIndex = nextCurrentIndex.child(rowCount - 1, 0); + } else { + nextCurrentIndex = currentIndex.parent(); + } + } + + // if we have no current or didn't find a sibling/parent use the last item of the whole tree + if (!nextCurrentIndex.isValid()) { + const QModelIndex rootIdx = m_filterModel->index(0, 0); + // if the tree does not contain any item - don't do anything + if (!rootIdx.isValid()) + return; + + // get the last (visible) top level index + nextCurrentIndex = m_filterModel->index(m_filterModel->rowCount(QModelIndex()) - 1, 0); + // step through until end + while (int rowCount = m_filterModel->rowCount(nextCurrentIndex)) + nextCurrentIndex = nextCurrentIndex.child(rowCount - 1, 0); + } + + m_treeView->setCurrentIndex(nextCurrentIndex); + onItemActivated(nextCurrentIndex); +} + +void TestResultsPane::onItemActivated(const QModelIndex &index) +{ + if (!index.isValid()) + return; + + const TestResult tr = m_filterModel->testResult(index); + if (!tr.fileName().isEmpty()) + Core::EditorManager::openEditorAt(tr.fileName(), tr.line(), 0); +} + +void TestResultsPane::onRunAllTriggered() +{ + TestRunner *runner = TestRunner::instance(); + runner->setSelectedTests(TestTreeModel::instance()->getAllTestCases()); + runner->prepareToRunTests(); +} + +void TestResultsPane::onRunSelectedTriggered() +{ + TestRunner *runner = TestRunner::instance(); + runner->setSelectedTests(TestTreeModel::instance()->getSelectedTests()); + runner->prepareToRunTests(); +} + +void TestResultsPane::initializeFilterMenu() +{ + const bool omitIntern = AutotestPlugin::instance()->settings()->omitInternalMssg; + // FilterModel has all messages enabled by default + if (omitIntern) + m_filterModel->toggleTestResultType(Result::MessageInternal); + + QMap textAndType; + textAndType.insert(Result::Pass, tr("Pass")); + textAndType.insert(Result::Fail, tr("Fail")); + textAndType.insert(Result::ExpectedFail, tr("Expected Fail")); + textAndType.insert(Result::UnexpectedPass, tr("Unexpected Pass")); + textAndType.insert(Result::Skip, tr("Skip")); + textAndType.insert(Result::Benchmark, tr("Benchmarks")); + textAndType.insert(Result::MessageDebug, tr("Debug Messages")); + textAndType.insert(Result::MessageWarn, tr("Warning Messages")); + textAndType.insert(Result::MessageInternal, tr("Internal Messages")); + foreach (Result::Type result, textAndType.keys()) { + QAction *action = new QAction(m_filterMenu); + action->setText(textAndType.value(result)); + action->setCheckable(true); + action->setChecked(result != Result::MessageInternal || !omitIntern); + action->setData(result); + m_filterMenu->addAction(action); + } + m_filterMenu->addSeparator(); + QAction *action = new QAction(m_filterMenu); + action->setText(tr("Check All Filters")); + action->setCheckable(false); + m_filterMenu->addAction(action); + connect(action, &QAction::triggered, this, &TestResultsPane::enableAllFilter); +} + +void TestResultsPane::updateSummaryLabel() +{ + QString labelText = QString::fromLatin1("

Test summary:   %1 %2, %3 %4") + .arg(QString::number(m_model->resultTypeCount(Result::Pass)), tr("passes"), + QString::number(m_model->resultTypeCount(Result::Fail)), tr("fails")); + int count = m_model->resultTypeCount(Result::UnexpectedPass); + if (count) + labelText.append(QString::fromLatin1(", %1 %2") + .arg(QString::number(count), tr("unexpected passes"))); + count = m_model->resultTypeCount(Result::ExpectedFail); + if (count) + labelText.append(QString::fromLatin1(", %1 %2") + .arg(QString::number(count), tr("expected fails"))); + count = m_model->resultTypeCount(Result::MessageFatal); + if (count) + labelText.append(QString::fromLatin1(", %1 %2") + .arg(QString::number(count), tr("fatals"))); + count = m_model->resultTypeCount(Result::BlacklistedFail) + + m_model->resultTypeCount(Result::BlacklistedPass); + if (count) + labelText.append(QString::fromLatin1(", %1 %2") + .arg(QString::number(count), tr("blacklisted"))); + + count = m_model->disabledTests(); + if (count) + labelText.append(tr(", %1 disabled").arg(count)); + + labelText.append(QLatin1String(".

")); + m_summaryLabel->setText(labelText); +} + +void TestResultsPane::enableAllFilter() +{ + foreach (QAction *action, m_filterMenu->actions()) { + if (action->isCheckable()) + action->setChecked(true); + } + m_filterModel->enableAllResultTypes(); +} + +void TestResultsPane::filterMenuTriggered(QAction *action) +{ + m_filterModel->toggleTestResultType(TestResult::toResultType(action->data().value())); + navigateStateChanged(); +} + +void TestResultsPane::onTestRunStarted() +{ + m_testRunning = true; + m_stopTestRun->setEnabled(true); + m_runAll->setEnabled(false); + m_runSelected->setEnabled(false); + m_summaryWidget->setVisible(false); +} + +void TestResultsPane::onTestRunFinished() +{ + m_testRunning = false; + m_stopTestRun->setEnabled(false); + m_runAll->setEnabled(true); + m_runSelected->setEnabled(true); + updateSummaryLabel(); + m_summaryWidget->setVisible(true); + m_model->removeCurrentTestMessage(); + disconnect(m_treeView->verticalScrollBar(), &QScrollBar::rangeChanged, + this, &TestResultsPane::onScrollBarRangeChanged); +} + +void TestResultsPane::onScrollBarRangeChanged(int, int max) +{ + if (m_autoScroll && m_atEnd) + m_treeView->verticalScrollBar()->setValue(max); +} + +void TestResultsPane::onTestTreeModelChanged() +{ + const bool enable = TestTreeModel::instance()->hasTests(); + m_runAll->setEnabled(enable); + m_runSelected->setEnabled(enable); +} + +void TestResultsPane::onCustomContextMenuRequested(const QPoint &pos) +{ + const bool resultsAvailable = m_filterModel->hasResults(); + const bool enabled = !m_testRunning && resultsAvailable; + const QModelIndex clicked = m_treeView->indexAt(pos); + QMenu menu; + QAction *action = new QAction(tr("Copy"), &menu); + action->setShortcut(QKeySequence(QKeySequence::Copy)); + action->setEnabled(resultsAvailable); + connect(action, &QAction::triggered, [this, clicked] () { + onCopyItemTriggered(clicked); + }); + menu.addAction(action); + + action = new QAction(tr("Copy All"), &menu); + action->setEnabled(enabled); + connect(action, &QAction::triggered, this, &TestResultsPane::onCopyWholeTriggered); + menu.addAction(action); + + action = new QAction(tr("Save Output to File..."), &menu); + action->setEnabled(enabled); + connect(action, &QAction::triggered, this, &TestResultsPane::onSaveWholeTriggered); + menu.addAction(action); + + menu.exec(m_treeView->mapToGlobal(pos)); +} + +void TestResultsPane::onCopyItemTriggered(const QModelIndex &idx) +{ + const TestResult result = m_filterModel->testResult(idx); + QApplication::clipboard()->setText(TestResultDelegate::outputString(result, true)); +} + +void TestResultsPane::onCopyWholeTriggered() +{ + QApplication::clipboard()->setText(getWholeOutput()); +} + +void TestResultsPane::onSaveWholeTriggered() +{ + const QString fileName = QFileDialog::getSaveFileName(Core::ICore::dialogParent(), + tr("Save Output To...")); + Utils::FileSaver saver(fileName, QIODevice::Text); + if (!saver.write(getWholeOutput().toUtf8()) || !saver.finalize()) { + QMessageBox::critical(Core::ICore::dialogParent(), tr("Error"), + tr("Failed to write \"%1\".\n\n%2").arg(fileName) + .arg(saver.errorString())); + } +} + +// helper for onCopyWholeTriggered() and onSaveWholeTriggered() +QString TestResultsPane::getWholeOutput(const QModelIndex &parent) +{ + QString output; + for (int row = 0, count = m_model->rowCount(parent); row < count; ++row) { + QModelIndex current = m_model->index(row, 0, parent); + const TestResult result = m_model->testResult(current); + output.append(TestResult::resultToString(result.result())).append(QLatin1Char('\t')); + output.append(TestResultDelegate::outputString(result, true)).append(QLatin1Char('\n')); + output.append(getWholeOutput(current)); + } + return output; +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testresultspane.h b/src/plugins/autotest/testresultspane.h new file mode 100644 index 00000000000..a50f75e9f46 --- /dev/null +++ b/src/plugins/autotest/testresultspane.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTRESULTSPANE_H +#define TESTRESULTSPANE_H + +#include + +#include + +QT_BEGIN_NAMESPACE +class QAction; +class QFrame; +class QKeyEvent; +class QLabel; +class QModelIndex; +class QMenu; +class QToolButton; +QT_END_NAMESPACE + +namespace Core { +class IContext; +} + +namespace Autotest { +namespace Internal { + +class TestResult; +class TestResultModel; +class TestResultFilterModel; + +class ResultsTreeView : public Utils::TreeView +{ + Q_OBJECT +public: + ResultsTreeView(QWidget *parent = 0); + +signals: + void copyShortcutTriggered(); + +protected: + void keyPressEvent(QKeyEvent *event); +}; + +class TestResultsPane : public Core::IOutputPane +{ + Q_OBJECT +public: + virtual ~TestResultsPane(); + static TestResultsPane *instance(); + + // IOutputPane interface + QWidget *outputWidget(QWidget *parent); + QList toolBarWidgets() const; + QString displayName() const; + int priorityInStatusBar() const; + void clearContents(); + void visibilityChanged(bool visible); + void setFocus(); + bool hasFocus() const; + bool canFocus() const; + bool canNavigate() const; + bool canNext() const; + bool canPrevious() const; + void goToNext(); + void goToPrev(); + +signals: + +public slots: + void addTestResult(TestResult *result); + +private slots: + void onItemActivated(const QModelIndex &index); + void onRunAllTriggered(); + void onRunSelectedTriggered(); + void enableAllFilter(); + void filterMenuTriggered(QAction *action); + +private: + explicit TestResultsPane(QObject *parent = 0); + void initializeFilterMenu(); + void updateSummaryLabel(); + void createToolButtons(); + void onTestRunStarted(); + void onTestRunFinished(); + void onScrollBarRangeChanged(int, int max); + void onTestTreeModelChanged(); + void onCustomContextMenuRequested(const QPoint &pos); + void onCopyItemTriggered(const QModelIndex &idx); + void onCopyWholeTriggered(); + void onSaveWholeTriggered(); + QString getWholeOutput(const QModelIndex &parent = QModelIndex()); + + QWidget *m_outputWidget; + QFrame *m_summaryWidget; + QLabel *m_summaryLabel; + ResultsTreeView *m_treeView; + TestResultModel *m_model; + TestResultFilterModel *m_filterModel; + Core::IContext *m_context; + QToolButton *m_expandCollapse; + QToolButton *m_runAll; + QToolButton *m_runSelected; + QToolButton *m_stopTestRun; + QToolButton *m_filterButton; + QMenu *m_filterMenu; + bool m_wasVisibleBefore; + bool m_autoScroll; + bool m_atEnd; + bool m_testRunning; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTRESULTSPANE_H diff --git a/src/plugins/autotest/testrunner.cpp b/src/plugins/autotest/testrunner.cpp new file mode 100644 index 00000000000..4914d6d26c9 --- /dev/null +++ b/src/plugins/autotest/testrunner.cpp @@ -0,0 +1,325 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "testrunner.h" + +#include "autotestconstants.h" +#include "autotestplugin.h" +#include "testresultspane.h" +#include "testsettings.h" +#include "testoutputreader.h" + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace Autotest { +namespace Internal { + +static TestRunner *m_instance = 0; + +static QString executableFilePath(const QString &command, const QProcessEnvironment &environment) +{ + if (command.isEmpty()) + return QString(); + + QFileInfo commandFileInfo(command); + if (commandFileInfo.isExecutable() && commandFileInfo.path() != QLatin1String(".")) { + return commandFileInfo.absoluteFilePath(); + } else if (commandFileInfo.path() == QLatin1String(".")){ + QString fullCommandFileName = command; + #ifdef Q_OS_WIN + if (!command.endsWith(QLatin1String(".exe"))) + fullCommandFileName = command + QLatin1String(".exe"); + + static const QString pathSeparator(QLatin1Char(';')); + #else + static const QString pathSeparator(QLatin1Char(':')); + #endif + QStringList pathList = environment.value(QLatin1String("PATH")).split(pathSeparator); + + foreach (const QString &path, pathList) { + QString filePath(path + QDir::separator() + fullCommandFileName); + if (QFileInfo(filePath).isExecutable()) + return commandFileInfo.absoluteFilePath(); + } + } + return QString(); +} + +TestRunner *TestRunner::instance() +{ + if (!m_instance) + m_instance = new TestRunner; + return m_instance; +} + +TestRunner::TestRunner(QObject *parent) : + QObject(parent), + m_executingTests(false) +{ + connect(&m_futureWatcher, &QFutureWatcher::resultReadyAt, + this, [this](int index) { emit testResultReady(m_futureWatcher.resultAt(index)); }); + connect(&m_futureWatcher, &QFutureWatcher::finished, + this, &TestRunner::onFinished); + connect(this, &TestRunner::requestStopTestRun, + &m_futureWatcher, &QFutureWatcher::cancel); + connect(&m_futureWatcher, &QFutureWatcher::canceled, + this, [this]() { emit testResultReady(new FaultyTestResult( + Result::MessageFatal, + QObject::tr("Test run canceled by user."))); + }); +} + +TestRunner::~TestRunner() +{ + qDeleteAll(m_selectedTests); + m_selectedTests.clear(); + m_instance = 0; +} + +void TestRunner::setSelectedTests(const QList &selected) +{ + qDeleteAll(m_selectedTests); + m_selectedTests.clear(); + m_selectedTests = selected; +} + +static void performTestRun(QFutureInterface &futureInterface, + const QList selectedTests, const int timeout, + const QString metricsOption) +{ + QEventLoop eventLoop; + int testCaseCount = 0; + foreach (TestConfiguration *config, selectedTests) { + config->completeTestInformation(); + if (config->project()) { + testCaseCount += config->testCaseCount(); + } else { + futureInterface.reportResult(new FaultyTestResult(Result::MessageWarn, + QObject::tr("Project is null for \"%1\". Removing from test run.\n" + "Check the test environment.").arg(config->displayName()))); + } + } + + QProcess testProcess; + testProcess.setReadChannelMode(QProcess::MergedChannels); + testProcess.setReadChannel(QProcess::StandardOutput); + + futureInterface.setProgressRange(0, testCaseCount); + futureInterface.setProgressValue(0); + + foreach (const TestConfiguration *testConfiguration, selectedTests) { + QScopedPointer outputReader; + switch (testConfiguration->testType()) { + case TestTypeQt: + outputReader.reset(new QtTestOutputReader(futureInterface, &testProcess)); + break; + case TestTypeGTest: + outputReader.reset(new GTestOutputReader(futureInterface, &testProcess)); + break; + } + if (futureInterface.isCanceled()) + break; + + if (!testConfiguration->project()) + continue; + + QProcessEnvironment environment = testConfiguration->environment().toProcessEnvironment(); + QString commandFilePath = executableFilePath(testConfiguration->targetFile(), environment); + if (commandFilePath.isEmpty()) { + futureInterface.reportResult(new FaultyTestResult(Result::MessageFatal, + QObject::tr("Could not find command \"%1\". (%2)") + .arg(testConfiguration->targetFile()) + .arg(testConfiguration->displayName()))); + continue; + } + + if (testConfiguration->testType() == TestTypeQt) { + QStringList argumentList(QLatin1String("-xml")); + if (!metricsOption.isEmpty()) + argumentList << metricsOption; + if (testConfiguration->testCases().count()) + argumentList << testConfiguration->testCases(); + testProcess.setArguments(argumentList); + } else { // TestTypeGTest + const QStringList &testSets = testConfiguration->testCases(); + if (testSets.size()) { + QStringList argumentList; + argumentList << QLatin1String("--gtest_filter=") + + testSets.join(QLatin1Char(':')); + testProcess.setArguments(argumentList); + } + } + + testProcess.setWorkingDirectory(testConfiguration->workingDirectory()); + if (Utils::HostOsInfo::isWindowsHost()) + environment.insert(QLatin1String("QT_LOGGING_TO_CONSOLE"), QLatin1String("1")); + testProcess.setProcessEnvironment(environment); + testProcess.setProgram(commandFilePath); + testProcess.start(); + + bool ok = testProcess.waitForStarted(); + QTime executionTimer; + executionTimer.start(); + bool canceledByTimeout = false; + if (ok) { + while (testProcess.state() == QProcess::Running) { + if (executionTimer.elapsed() >= timeout) { + canceledByTimeout = true; + break; + } + if (futureInterface.isCanceled()) { + testProcess.kill(); + testProcess.waitForFinished(); + return; + } + eventLoop.processEvents(); + } + } + + if (canceledByTimeout) { + if (testProcess.state() != QProcess::NotRunning) { + testProcess.kill(); + testProcess.waitForFinished(); + } + futureInterface.reportResult(new FaultyTestResult(Result::MessageFatal, QObject::tr( + "Test case canceled due to timeout. \nMaybe raise the timeout?"))); + } + } + futureInterface.setProgressValue(testCaseCount); +} + +void TestRunner::prepareToRunTests() +{ + ProjectExplorer::Internal::ProjectExplorerSettings projectExplorerSettings = + ProjectExplorer::ProjectExplorerPlugin::projectExplorerSettings(); + if (projectExplorerSettings.buildBeforeDeploy && !projectExplorerSettings.saveBeforeBuild) { + if (!ProjectExplorer::ProjectExplorerPlugin::saveModifiedFiles()) + return; + } + + const bool omitRunConfigWarnings = AutotestPlugin::instance()->settings()->omitRunConfigWarn; + + m_executingTests = true; + emit testRunStarted(); + + // clear old log and output pane + TestResultsPane::instance()->clearContents(); + + foreach (TestConfiguration *config, m_selectedTests) { + if (!omitRunConfigWarnings && config->guessedConfiguration()) { + emit testResultReady(new FaultyTestResult(Result::MessageWarn, + tr("Project's run configuration was guessed for \"%1\".\n" + "This might cause trouble during execution.").arg(config->displayName()))); + } + } + + if (m_selectedTests.empty()) { + emit testResultReady(new FaultyTestResult(Result::MessageWarn, + tr("No tests selected. Canceling test run."))); + onFinished(); + return; + } + + ProjectExplorer::Project *project = m_selectedTests.at(0)->project(); + if (!project) { + emit testResultReady(new FaultyTestResult(Result::MessageWarn, + tr("Project is null. Canceling test run.\n" + "Only desktop kits are supported. Make sure the " + "currently active kit is a desktop kit."))); + onFinished(); + return; + } + + if (!projectExplorerSettings.buildBeforeDeploy) { + runTests(); + } else { + if (project->hasActiveBuildSettings()) { + buildProject(project); + } else { + emit testResultReady(new FaultyTestResult(Result::MessageFatal, + tr("Project is not configured. Canceling test run."))); + onFinished(); + return; + } + } +} + +void TestRunner::runTests() +{ + const QSharedPointer settings = AutotestPlugin::instance()->settings(); + const QString &metricsOption = TestSettings::metricsTypeToOption(settings->metrics); + + QFuture future = Utils::runAsync(&performTestRun, m_selectedTests, + settings->timeout, metricsOption); + m_futureWatcher.setFuture(future); + Core::ProgressManager::addTask(future, tr("Running Tests"), Autotest::Constants::TASK_INDEX); +} + +void TestRunner::buildProject(ProjectExplorer::Project *project) +{ + ProjectExplorer::BuildManager *buildManager = ProjectExplorer::BuildManager::instance(); + m_buildConnect = connect(this, &TestRunner::requestStopTestRun, + buildManager, &ProjectExplorer::BuildManager::cancel); + connect(buildManager, &ProjectExplorer::BuildManager::buildQueueFinished, + this, &TestRunner::buildFinished); + ProjectExplorer::ProjectExplorerPlugin::buildProject(project); +} + +void TestRunner::buildFinished(bool success) +{ + disconnect(m_buildConnect); + ProjectExplorer::BuildManager *buildManager = ProjectExplorer::BuildManager::instance(); + disconnect(buildManager, &ProjectExplorer::BuildManager::buildQueueFinished, + this, &TestRunner::buildFinished); + + if (success) { + runTests(); + } else { + emit testResultReady(new FaultyTestResult(Result::MessageFatal, + tr("Build failed. Canceling test run."))); + onFinished(); + } +} + +void TestRunner::onFinished() +{ + m_executingTests = false; + emit testRunFinished(); +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testrunner.h b/src/plugins/autotest/testrunner.h new file mode 100644 index 00000000000..c51edb95b11 --- /dev/null +++ b/src/plugins/autotest/testrunner.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTRUNNER_H +#define TESTRUNNER_H + +#include "testconfiguration.h" +#include "testresult.h" + +#include +#include +#include + +namespace ProjectExplorer { +class Project; +} + +namespace Autotest { +namespace Internal { + +class TestRunner : public QObject +{ + Q_OBJECT + +public: + static TestRunner* instance(); + ~TestRunner(); + + void setSelectedTests(const QList &selected); + bool isTestRunning() const { return m_executingTests; } + +signals: + void testRunStarted(); + void testRunFinished(); + void requestStopTestRun(); + void testResultReady(TestResult *result); + +public slots: + void prepareToRunTests(); + +private slots: + void buildProject(ProjectExplorer::Project *project); + void buildFinished(bool success); + void onFinished(); + +private: + void runTests(); + explicit TestRunner(QObject *parent = 0); + + QFutureWatcher m_futureWatcher; + QList m_selectedTests; + bool m_executingTests; + + // temporarily used if building before running is necessary + QMetaObject::Connection m_buildConnect; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTRUNNER_H diff --git a/src/plugins/autotest/testsettings.cpp b/src/plugins/autotest/testsettings.cpp new file mode 100644 index 00000000000..e4c38d9d3d9 --- /dev/null +++ b/src/plugins/autotest/testsettings.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "testsettings.h" + +#include + +namespace Autotest { +namespace Internal { + +static const char group[] = "Autotest"; +static const char timeoutKey[] = "Timeout"; +static const char metricsKey[] = "Metrics"; +static const char omitInternalKey[] = "OmitInternal"; +static const char omitRunConfigWarnKey[] = "OmitRCWarnings"; +static const char limitResultOutputKey[] = "LimitResultOutput"; +static const char autoScrollKey[] = "AutoScrollResults"; +static const int defaultTimeout = 60000; + +TestSettings::TestSettings() + : timeout(defaultTimeout), metrics(Walltime), omitInternalMssg(true), omitRunConfigWarn(false), + limitResultOutput(true), autoScroll(true) +{ +} + +void TestSettings::toSettings(QSettings *s) const +{ + s->beginGroup(QLatin1String(group)); + s->setValue(QLatin1String(timeoutKey), timeout); + s->setValue(QLatin1String(metricsKey), metrics); + s->setValue(QLatin1String(omitInternalKey), omitInternalMssg); + s->setValue(QLatin1String(omitRunConfigWarnKey), omitRunConfigWarn); + s->setValue(QLatin1String(limitResultOutputKey), limitResultOutput); + s->setValue(QLatin1String(autoScrollKey), autoScroll); + s->endGroup(); +} + +static MetricsType intToMetrics(int value) +{ + switch (value) { + case Walltime: + return Walltime; + case TickCounter: + return TickCounter; + case EventCounter: + return EventCounter; + case CallGrind: + return CallGrind; + case Perf: + return Perf; + default: + return Walltime; + } +} + +void TestSettings::fromSettings(const QSettings *s) +{ + const QString root = QLatin1String(group) + QLatin1Char('/'); + timeout = s->value(root + QLatin1String(timeoutKey), defaultTimeout).toInt(); + metrics = intToMetrics(s->value(root + QLatin1String(metricsKey), Walltime).toInt()); + omitInternalMssg = s->value(root + QLatin1String(omitInternalKey), true).toBool(); + omitRunConfigWarn = s->value(root + QLatin1String(omitRunConfigWarnKey), false).toBool(); + limitResultOutput = s->value(root + QLatin1String(limitResultOutputKey), true).toBool(); + autoScroll = s->value(root + QLatin1String(autoScrollKey), true).toBool(); +} + +bool TestSettings::equals(const TestSettings &rhs) const +{ + return timeout == rhs.timeout && metrics == rhs.metrics + && omitInternalMssg == rhs.omitInternalMssg + && omitRunConfigWarn == rhs.omitRunConfigWarn + && limitResultOutput == rhs.limitResultOutput + && autoScroll == rhs.autoScroll; +} + +QString TestSettings::metricsTypeToOption(const MetricsType type) +{ + switch (type) { + case MetricsType::Walltime: + return QString(); + case MetricsType::TickCounter: + return QLatin1String("-tickcounter"); + case MetricsType::EventCounter: + return QLatin1String("-eventcounter"); + case MetricsType::CallGrind: + return QLatin1String("-callgrind"); + case MetricsType::Perf: + return QLatin1String("-perf"); + default: + return QString(); + } +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testsettings.h b/src/plugins/autotest/testsettings.h new file mode 100644 index 00000000000..30b7b8f54b8 --- /dev/null +++ b/src/plugins/autotest/testsettings.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTSETTINGS_H +#define TESTSETTINGS_H + +#include + +QT_BEGIN_NAMESPACE +class QSettings; +QT_END_NAMESPACE + +namespace Autotest { +namespace Internal { + +enum MetricsType { + Walltime, + TickCounter, + EventCounter, + CallGrind, + Perf +}; + +struct TestSettings +{ + TestSettings(); + void toSettings(QSettings *s) const; + void fromSettings(const QSettings *s); + bool equals(const TestSettings &rhs) const; + static QString metricsTypeToOption(const MetricsType type); + + int timeout; + MetricsType metrics; + bool omitInternalMssg; + bool omitRunConfigWarn; + bool limitResultOutput; + bool autoScroll; +}; + +inline bool operator==(const TestSettings &s1, const TestSettings &s2) { return s1.equals(s2); } +inline bool operator!=(const TestSettings &s1, const TestSettings &s2) { return !s1.equals(s2); } + +} // namespace Internal +} // namespace Autotest + +#endif // TESTSETTINGS_H diff --git a/src/plugins/autotest/testsettingspage.cpp b/src/plugins/autotest/testsettingspage.cpp new file mode 100644 index 00000000000..fcc5508e70d --- /dev/null +++ b/src/plugins/autotest/testsettingspage.cpp @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "autotestconstants.h" +#include "testsettingspage.h" +#include "testsettings.h" + +#include + +#include + +namespace Autotest { +namespace Internal { + +TestSettingsWidget::TestSettingsWidget(QWidget *parent) + : QWidget(parent) +{ + m_ui.setupUi(this); + m_ui.callgrindRB->setEnabled(Utils::HostOsInfo::isAnyUnixHost()); // valgrind available on UNIX + m_ui.perfRB->setEnabled(Utils::HostOsInfo::isLinuxHost()); // according to docs perf Linux only +} + +void TestSettingsWidget::setSettings(const TestSettings &settings) +{ + m_ui.timeoutSpin->setValue(settings.timeout / 1000); // we store milliseconds + m_ui.omitInternalMsgCB->setChecked(settings.omitInternalMssg); + m_ui.omitRunConfigWarnCB->setChecked(settings.omitRunConfigWarn); + m_ui.limitResultOutputCB->setChecked(settings.limitResultOutput); + m_ui.autoScrollCB->setChecked(settings.autoScroll); + + switch (settings.metrics) { + case MetricsType::Walltime: + m_ui.walltimeRB->setChecked(true); + break; + case MetricsType::TickCounter: + m_ui.tickcounterRB->setChecked(true); + break; + case MetricsType::EventCounter: + m_ui.eventCounterRB->setChecked(true); + break; + case MetricsType::CallGrind: + m_ui.callgrindRB->setChecked(true); + break; + case MetricsType::Perf: + m_ui.perfRB->setChecked(true); + break; + default: + m_ui.walltimeRB->setChecked(true); + } +} + +TestSettings TestSettingsWidget::settings() const +{ + TestSettings result; + result.timeout = m_ui.timeoutSpin->value() * 1000; // we display seconds + result.omitInternalMssg = m_ui.omitInternalMsgCB->isChecked(); + result.omitRunConfigWarn = m_ui.omitRunConfigWarnCB->isChecked(); + result.limitResultOutput = m_ui.limitResultOutputCB->isChecked(); + result.autoScroll = m_ui.autoScrollCB->isChecked(); + + if (m_ui.walltimeRB->isChecked()) + result.metrics = MetricsType::Walltime; + else if (m_ui.tickcounterRB->isChecked()) + result.metrics = MetricsType::TickCounter; + else if (m_ui.eventCounterRB->isChecked()) + result.metrics = MetricsType::EventCounter; + else if (m_ui.callgrindRB->isChecked()) + result.metrics = MetricsType::CallGrind; + else if (m_ui.perfRB->isChecked()) + result.metrics = MetricsType::Perf; + + return result; +} + +TestSettingsPage::TestSettingsPage(const QSharedPointer &settings) + : m_settings(settings), m_widget(0) +{ + setId("A.AutoTest.General"); + setDisplayName(tr("General")); + setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); + setDisplayCategory(tr("Test Settings")); + setCategoryIcon(QLatin1String(":/images/autotest.png")); +} + +TestSettingsPage::~TestSettingsPage() +{ +} + +QWidget *TestSettingsPage::widget() +{ + if (!m_widget) { + m_widget = new TestSettingsWidget; + m_widget->setSettings(*m_settings); + } + return m_widget; +} + +void TestSettingsPage::apply() +{ + if (!m_widget) // page was not shown at all + return; + const TestSettings newSettings = m_widget->settings(); + if (newSettings != *m_settings) { + *m_settings = newSettings; + m_settings->toSettings(Core::ICore::settings()); + } +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testsettingspage.h b/src/plugins/autotest/testsettingspage.h new file mode 100644 index 00000000000..c41929c6677 --- /dev/null +++ b/src/plugins/autotest/testsettingspage.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTSETTINGSPAGE_H +#define TESTSETTINGSPAGE_H + +#include "ui_testsettingspage.h" + +#include + +#include + +namespace Autotest { +namespace Internal { + +struct TestSettings; + +class TestSettingsWidget : public QWidget +{ + Q_OBJECT +public: + explicit TestSettingsWidget(QWidget *parent = 0); + + void setSettings(const TestSettings &settings); + TestSettings settings() const; + +private: + Ui::TestSettingsPage m_ui; + +}; + +class TestSettingsPage : public Core::IOptionsPage +{ + Q_OBJECT +public: + explicit TestSettingsPage(const QSharedPointer &settings); + ~TestSettingsPage(); + + QWidget *widget(); + void apply(); + void finish() { } + +private: + QSharedPointer m_settings; + QPointer m_widget; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTSETTINGSPAGE_H diff --git a/src/plugins/autotest/testsettingspage.ui b/src/plugins/autotest/testsettingspage.ui new file mode 100644 index 00000000000..1822cfbbd9b --- /dev/null +++ b/src/plugins/autotest/testsettingspage.ui @@ -0,0 +1,261 @@ + + + Autotest::Internal::TestSettingsPage + + + + 0 + 0 + 407 + 228 + + + + Form + + + + + + + + Timeout used when executing each test case. + + + Timeout: + + + + + + + Timeout used when executing test cases. This will apply for each test case on its own, not the whole project. + + + s + + + 5 + + + 36000 + + + 60 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + Benchmark Metrics + + + + + + + 0 + 0 + + + + Uses walltime metrics for executing benchmarks (default). + + + Walltime + + + true + + + + + + + + 0 + 0 + + + + Uses tick counter when executing benchmarks. + + + Tick counter + + + + + + + + 0 + 0 + + + + Uses event counter when executing benchmarks. + + + Event counter + + + + + + + false + + + + 0 + 0 + + + + Uses Valgrind Callgrind when executing benchmarks (it must be installed). + + + Callgrind + + + + + + + false + + + + 0 + 0 + + + + Uses Perf when executing benchmarks (it must be installed). + + + Perf + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + General + + + + + + Hides internal messages by default. You can still enable them by using the test results filter. + + + Omit internal messages + + + true + + + + + + + Hides warnings related to a guessed run configuration. + + + Omit run configuration warnings + + + + + + + Limit result output to 100000 characters. + + + Limit result output + + + true + + + + + + + Automatically scroll down when new items are added and scrollbar is at bottom. + + + Automatically scroll results + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + diff --git a/src/plugins/autotest/testtreeitem.cpp b/src/plugins/autotest/testtreeitem.cpp new file mode 100644 index 00000000000..2011b4412cc --- /dev/null +++ b/src/plugins/autotest/testtreeitem.cpp @@ -0,0 +1,285 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "autotestconstants.h" +#include "testtreeitem.h" + +#include + +#include + +#include + +namespace Autotest { +namespace Internal { + +TestTreeItem::TestTreeItem(const QString &name, const QString &filePath, Type type) + : TreeItem( { name } ), + m_name(name), + m_filePath(filePath), + m_type(type), + m_line(0), + m_state(Enabled) +{ + switch (m_type) { + case TestClass: + case TestFunction: + case GTestCase: + case GTestCaseParameterized: + case GTestName: + m_checked = Qt::Checked; + break; + default: + m_checked = Qt::Unchecked; + } +} + +TestTreeItem::~TestTreeItem() +{ + removeChildren(); +} + +TestTreeItem::TestTreeItem(const TestTreeItem &other) + : TreeItem( { other.m_name } ), + m_name(other.m_name), + m_filePath(other.m_filePath), + m_checked(other.m_checked), + m_type(other.m_type), + m_line(other.m_line), + m_column(other.m_column), + m_mainFile(other.m_mainFile), + m_state(other.m_state) +{ + for (int row = 0, count = other.childCount(); row < count; ++row) + appendChild(new TestTreeItem(*childItem(row))); +} + +static QIcon testTreeIcon(TestTreeItem::Type type) +{ + static QIcon icons[] = { + QIcon(), + QIcon(QLatin1String(":/images/class.png")), + QIcon(QLatin1String(":/images/func.png")), + QIcon(QLatin1String(":/images/data.png")) + }; + if (type == TestTreeItem::GTestCase || type == TestTreeItem::GTestCaseParameterized) + return icons[1]; + + if (int(type) >= int(sizeof icons / sizeof *icons)) + return icons[2]; + return icons[type]; +} + +QVariant TestTreeItem::data(int /*column*/, int role) const +{ + switch (role) { + case Qt::DisplayRole: + if (m_type == Root && childCount() == 0) + return QString(m_name + QObject::tr(" (none)")); + else if (m_name.isEmpty()) + return QObject::tr(Constants::UNNAMED_QUICKTESTS); + else if (m_type == GTestCaseParameterized) + return QString(m_name + QObject::tr(" [parameterized]")); + else + return m_name; + case Qt::ToolTipRole: + if (m_type == TestClass && m_name.isEmpty()) { + return QObject::tr("

Give all test cases a name to ensure correct behavior " + "when running test cases and to be able to select them.

"); + } + return m_filePath; + case Qt::DecorationRole: + return testTreeIcon(m_type); + case Qt::CheckStateRole: + switch (m_type) { + case Root: + case TestDataFunction: + case TestSpecialFunction: + case TestDataTag: + return QVariant(); + case TestClass: + case GTestCase: + case GTestCaseParameterized: + return m_name.isEmpty() ? QVariant() : checked(); + case TestFunction: + case GTestName: + if (parentItem() && parentItem()->name().isEmpty()) + return QVariant(); + return checked(); + default: + return checked(); + } + case LinkRole: { + QVariant itemLink; + itemLink.setValue(TextEditor::TextEditorWidget::Link(m_filePath, m_line, m_column)); + return itemLink; + } + case ItalicRole: + switch (m_type) { + case TestDataFunction: + case TestSpecialFunction: + return true; + case TestClass: + return m_name.isEmpty(); + case TestFunction: + return parentItem() ? parentItem()->name().isEmpty() : false; + default: + return false; + } + case TypeRole: + return m_type; + case StateRole: + return (int)m_state; + } + return QVariant(); +} + +bool TestTreeItem::setData(int /*column*/, const QVariant &data, int role) +{ + if (role == Qt::CheckStateRole) { + Qt::CheckState old = checked(); + setChecked((Qt::CheckState)data.toInt()); + return checked() != old; + } + return false; +} + +bool TestTreeItem::modifyContent(const TestTreeItem *modified) +{ + bool hasBeenModified = false; + if (m_filePath != modified->m_filePath) { + m_filePath = modified->m_filePath; + hasBeenModified = true; + } + if (m_name != modified->m_name) { + m_name = modified->m_name; + hasBeenModified = true; + } + if (m_line != modified->m_line) { + m_line = modified->m_line; + hasBeenModified = true; + } + if (m_mainFile != modified->m_mainFile) { + m_mainFile = modified->m_mainFile; + hasBeenModified = true; + } + if (m_type != modified->m_type) { + m_type = modified->m_type; + hasBeenModified = true; + } + if (m_state != modified->m_state) { + m_state = modified->m_state; + hasBeenModified = true; + } + return hasBeenModified; +} + +void TestTreeItem::setChecked(const Qt::CheckState checkState) +{ + switch (m_type) { + case TestFunction: + case GTestName: { + m_checked = (checkState == Qt::Unchecked ? Qt::Unchecked : Qt::Checked); + parentItem()->revalidateCheckState(); + break; + } + case TestClass: + case GTestCase: + case GTestCaseParameterized: { + Qt::CheckState usedState = (checkState == Qt::Unchecked ? Qt::Unchecked : Qt::Checked); + for (int row = 0, count = childCount(); row < count; ++row) + childItem(row)->setChecked(usedState); + m_checked = usedState; + } + default: + return; + } +} + +Qt::CheckState TestTreeItem::checked() const +{ + switch (m_type) { + case TestClass: + case TestFunction: + case GTestCase: + case GTestCaseParameterized: + case GTestName: + return m_checked; + case TestDataFunction: + case TestSpecialFunction: + return Qt::Unchecked; + default: + if (parent()) + return parentItem()->m_checked; + } + return Qt::Unchecked; +} + +QList TestTreeItem::getChildNames() const +{ + QList names; + for (int row = 0, count = childCount(); row < count; ++row) + names << childItem(row)->name(); + return names; +} + +TestTreeItem *TestTreeItem::parentItem() const +{ + return static_cast(parent()); +} + +TestTreeItem *TestTreeItem::childItem(int row) const +{ + return static_cast(child(row)); +} + +void TestTreeItem::revalidateCheckState() +{ + if (childCount() == 0) + return; + bool foundChecked = false; + bool foundUnchecked = false; + for (int row = 0, count = childCount(); row < count; ++row) { + TestTreeItem *child = childItem(row); + switch (child->type()) { + case TestDataFunction: + case TestSpecialFunction: + continue; + default: + break; + } + + foundChecked |= (child->checked() != Qt::Unchecked); + foundUnchecked |= (child->checked() == Qt::Unchecked); + if (foundChecked && foundUnchecked) { + m_checked = Qt::PartiallyChecked; + return; + } + } + m_checked = (foundUnchecked ? Qt::Unchecked : Qt::Checked); +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testtreeitem.h b/src/plugins/autotest/testtreeitem.h new file mode 100644 index 00000000000..325486b6694 --- /dev/null +++ b/src/plugins/autotest/testtreeitem.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTTREEITEM_H +#define TESTTREEITEM_H + +#include + +#include +#include +#include + +namespace { + enum ItemRole { + LinkRole = Qt::UserRole + 2, // can be removed if AnnotationRole comes back + ItalicRole, // used only inside the delegate + TypeRole, + StateRole + }; +} + +namespace Autotest { +namespace Internal { + +class TestTreeItem : public Utils::TreeItem +{ + +public: + enum Type + { + Root, + TestClass, + TestFunction, + TestDataTag, + TestDataFunction, + TestSpecialFunction, + GTestCase, + GTestCaseParameterized, + GTestName + }; + + enum TestState + { + Enabled = 0x00, + Disabled = 0x01, + }; + + Q_FLAGS(TestState) + Q_DECLARE_FLAGS(TestStates, TestState) + + TestTreeItem(const QString &name = QString(), const QString &filePath = QString(), + Type type = Root); + virtual ~TestTreeItem(); + TestTreeItem(const TestTreeItem& other); + + virtual QVariant data(int column, int role) const override; + virtual bool setData(int column, const QVariant &data, int role) override; + bool modifyContent(const TestTreeItem *modified); + + const QString name() const { return m_name; } + const QString filePath() const { return m_filePath; } + void setLine(unsigned line) { m_line = line;} + unsigned line() const { return m_line; } + void setColumn(unsigned column) { m_column = column; } + unsigned column() const { return m_column; } + QString mainFile() const { return m_mainFile; } + void setMainFile(const QString &mainFile) { m_mainFile = mainFile; } + void setChecked(const Qt::CheckState checked); + Qt::CheckState checked() const; + Type type() const { return m_type; } + void setState(TestStates states) { m_state = states; } + TestStates state() const { return m_state; } + QList getChildNames() const; + TestTreeItem *parentItem() const; + TestTreeItem *childItem(int row) const; + +private: + void revalidateCheckState(); + + QString m_name; + QString m_filePath; + Qt::CheckState m_checked; + Type m_type; + unsigned m_line; + unsigned m_column; + QString m_mainFile; // main for Quick tests, project file for gtest + TestStates m_state; +}; + +struct TestCodeLocationAndType { + QString m_name; // tag name for m_type == TEST_DATATAG, file name for other values + unsigned m_line; + unsigned m_column; + TestTreeItem::Type m_type; + TestTreeItem::TestStates m_state; +}; + +typedef QVector TestCodeLocationList; + +} // namespace Internal +} // namespace Autotest + +Q_DECLARE_METATYPE(Autotest::Internal::TestTreeItem *) +Q_DECLARE_METATYPE(Autotest::Internal::TestCodeLocationAndType) + +#endif // TESTTREEITEM_H diff --git a/src/plugins/autotest/testtreeitemdelegate.cpp b/src/plugins/autotest/testtreeitemdelegate.cpp new file mode 100644 index 00000000000..e5cc237aa56 --- /dev/null +++ b/src/plugins/autotest/testtreeitemdelegate.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "testtreeitem.h" +#include "testtreeitemdelegate.h" +#include "testtreemodel.h" + +#include + +namespace Autotest { +namespace Internal { + +TestTreeItemDelegate::TestTreeItemDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + +TestTreeItemDelegate::~TestTreeItemDelegate() +{ +} + +void TestTreeItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + bool italic = index.data(ItalicRole).toBool(); + if (italic) { + QFont font(option.font); + font.setItalic(true); + opt.font = font; + + // correct margin of items without a checkbox (except for root items) + QStyleOptionButton styleOpt; + styleOpt.initFrom(opt.widget); + const QSize sz; // no text, no icon - we just need the size of the check box + QSize cbSize = opt.widget->style()->sizeFromContents(QStyle::CT_CheckBox, &styleOpt, sz); + // the 6 results from hard coded margins of the checkbox itself (2x2) and the item (1x2) + opt.rect.setLeft(opt.rect.left() + cbSize.width() + 6); + + // HACK make sure the pixels that have been moved right are painted for selections + if (opt.state & QStyle::State_Selected) { + QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) + cg = QPalette::Inactive; + painter->fillRect(option.rect, opt.palette.brush(cg, QPalette::Highlight)); + } + } + + // paint disabled googletests in gray + if (index.data(StateRole).toInt() & TestTreeItem::Disabled) + opt.palette.setColor(QPalette::Text, QColor(0xa0, 0xa0, 0xa0)); + + QStyledItemDelegate::paint(painter, opt, index); +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testtreeitemdelegate.h b/src/plugins/autotest/testtreeitemdelegate.h new file mode 100644 index 00000000000..0391ea28d30 --- /dev/null +++ b/src/plugins/autotest/testtreeitemdelegate.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTTREEITEMDELEGATE_H +#define TESTTREEITEMDELEGATE_H + +#include + +namespace Autotest { +namespace Internal { + +class TestTreeItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + TestTreeItemDelegate(QObject *parent = 0); + ~TestTreeItemDelegate(); + +public: + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTTREEITEMDELEGATE_H diff --git a/src/plugins/autotest/testtreemodel.cpp b/src/plugins/autotest/testtreemodel.cpp new file mode 100644 index 00000000000..65b7759315e --- /dev/null +++ b/src/plugins/autotest/testtreemodel.cpp @@ -0,0 +1,947 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "autotestconstants.h" +#include "testcodeparser.h" +#include "testtreeitem.h" +#include "testtreemodel.h" + +#include + +#include +#include + +#include + +#include + +#include + +namespace Autotest { +namespace Internal { + +TestTreeModel::TestTreeModel(QObject *parent) : + TreeModel(parent), + m_autoTestRootItem(new TestTreeItem(tr("Auto Tests"), QString(), TestTreeItem::Root)), + m_quickTestRootItem(new TestTreeItem(tr("Qt Quick Tests"), QString(), TestTreeItem::Root)), + m_googleTestRootItem(new TestTreeItem(tr("Google Tests"), QString(), TestTreeItem::Root)), + m_parser(new TestCodeParser(this)), + m_connectionsInitialized(false) +{ + rootItem()->appendChild(m_autoTestRootItem); + rootItem()->appendChild(m_quickTestRootItem); + rootItem()->appendChild(m_googleTestRootItem); + + connect(m_parser, &TestCodeParser::cacheCleared, this, + &TestTreeModel::removeAllTestItems, Qt::QueuedConnection); + connect(m_parser, &TestCodeParser::testItemCreated, + this, &TestTreeModel::addTestTreeItem, Qt::QueuedConnection); + connect(m_parser, &TestCodeParser::testItemModified, + this, &TestTreeModel::modifyTestTreeItem, Qt::QueuedConnection); + connect(m_parser, &TestCodeParser::testItemsRemoved, + this, &TestTreeModel::removeTestTreeItems, Qt::QueuedConnection); + connect(m_parser, &TestCodeParser::unnamedQuickTestsUpdated, + this, &TestTreeModel::updateUnnamedQuickTest, Qt::QueuedConnection); + connect(m_parser, &TestCodeParser::unnamedQuickTestsRemoved, + this, &TestTreeModel::removeUnnamedQuickTests, Qt::QueuedConnection); + connect(m_parser, &TestCodeParser::gTestsRemoved, + this, &TestTreeModel::removeGTests, Qt::QueuedConnection); + +// CppTools::CppModelManagerInterface *cppMM = CppTools::CppModelManagerInterface::instance(); +// if (cppMM) { +// // replace later on by +// // cppMM->registerAstProcessor([this](const CplusPlus::Document::Ptr &doc, +// // const CPlusPlus::Snapshot &snapshot) { +// // checkForQtTestStuff(doc, snapshot); +// // }); +// connect(cppMM, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)), +// this, SLOT(checkForQtTestStuff(CPlusPlus::Document::Ptr)), +// Qt::DirectConnection); + +// } +} + +static TestTreeModel *m_instance = 0; + +TestTreeModel *TestTreeModel::instance() +{ + if (!m_instance) + m_instance = new TestTreeModel; + return m_instance; +} + +TestTreeModel::~TestTreeModel() +{ + m_instance = 0; +} + +void TestTreeModel::enableParsing() +{ + m_refCounter.ref(); + + if (!m_connectionsInitialized) + m_parser->setDirty(); + + m_parser->setState(TestCodeParser::Idle); + if (m_connectionsInitialized) + return; + + ProjectExplorer::SessionManager *sm = ProjectExplorer::SessionManager::instance(); + connect(sm, &ProjectExplorer::SessionManager::startupProjectChanged, + m_parser, &TestCodeParser::onStartupProjectChanged); + + CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + connect(cppMM, &CppTools::CppModelManager::documentUpdated, + m_parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection); + connect(cppMM, &CppTools::CppModelManager::aboutToRemoveFiles, + m_parser, &TestCodeParser::removeFiles, Qt::QueuedConnection); + connect(cppMM, &CppTools::CppModelManager::projectPartsUpdated, + m_parser, &TestCodeParser::onProjectPartsUpdated); + + QmlJS::ModelManagerInterface *qmlJsMM = QmlJS::ModelManagerInterface::instance(); + connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated, + m_parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection); + connect(qmlJsMM, &QmlJS::ModelManagerInterface::aboutToRemoveFiles, + m_parser, &TestCodeParser::removeFiles, Qt::QueuedConnection); + m_connectionsInitialized = true; +} + +void TestTreeModel::disableParsing() +{ + if (!m_refCounter.deref()) + m_parser->setState(TestCodeParser::Disabled); +} + +bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + TestTreeItem *item = static_cast(index.internalPointer()); + if (item && item->setData(index.column(), value, role)) { + emit dataChanged(index, index); + if (role == Qt::CheckStateRole) { + switch (item->type()) { + case TestTreeItem::TestClass: + case TestTreeItem::GTestCase: + case TestTreeItem::GTestCaseParameterized: + if (item->childCount() > 0) + emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0)); + break; + case TestTreeItem::TestFunction: + case TestTreeItem::GTestName: + emit dataChanged(index.parent(), index.parent()); + break; + default: // avoid warning regarding unhandled enum member + break; + } + } + return true; + } + return false; +} + +Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + + TestTreeItem *item = static_cast(itemForIndex(index)); + switch(item->type()) { + case TestTreeItem::TestClass: + case TestTreeItem::GTestCase: + case TestTreeItem::GTestCaseParameterized: + if (item->name().isEmpty()) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable; + case TestTreeItem::TestFunction: + case TestTreeItem::GTestName: + if (item->parentItem()->name().isEmpty()) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; + case TestTreeItem::Root: + return Qt::ItemIsEnabled; + case TestTreeItem::TestDataFunction: + case TestTreeItem::TestSpecialFunction: + case TestTreeItem::TestDataTag: + default: + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } +} + +bool TestTreeModel::hasTests() const +{ + return m_autoTestRootItem->childCount() > 0 || m_quickTestRootItem->childCount() > 0 + || m_googleTestRootItem->childCount() > 0; +} + +QList TestTreeModel::getAllTestCases() const +{ + QList result; + + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (!project) + return result; + + // get all Auto Tests + for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) { + const TestTreeItem *child = m_autoTestRootItem->childItem(row); + + TestConfiguration *tc = new TestConfiguration(child->name(), QStringList(), + child->childCount()); + tc->setMainFilePath(child->filePath()); + tc->setProject(project); + result << tc; + } + + // get all Quick Tests + QMap foundMains; + for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) { + const TestTreeItem *child = m_quickTestRootItem->childItem(row); + // unnamed Quick Tests must be handled separately + if (child->name().isEmpty()) { + for (int childRow = 0, ccount = child->childCount(); childRow < ccount; ++ childRow) { + const TestTreeItem *grandChild = child->childItem(childRow); + const QString mainFile = grandChild->mainFile(); + foundMains.insert(mainFile, foundMains.contains(mainFile) + ? foundMains.value(mainFile) + 1 : 1); + } + continue; + } + // named Quick Test + const QString mainFile = child->mainFile(); + foundMains.insert(mainFile, foundMains.contains(mainFile) + ? foundMains.value(mainFile) + child->childCount() + : child->childCount()); + } + // create TestConfiguration for each main + foreach (const QString &mainFile, foundMains.keys()) { + TestConfiguration *tc = new TestConfiguration(QString(), QStringList(), + foundMains.value(mainFile)); + tc->setMainFilePath(mainFile); + tc->setProject(project); + result << tc; + } + + foundMains.clear(); + + // get all Google Tests + for (int row = 0, count = m_googleTestRootItem->childCount(); row < count; ++row) { + const TestTreeItem *child = m_googleTestRootItem->childItem(row); + for (int childRow = 0, childCount = child->childCount(); childRow < childCount; ++childRow) { + const QString &proFilePath = child->childItem(childRow)->mainFile(); + foundMains.insert(proFilePath, foundMains.contains(proFilePath) + ? foundMains.value(proFilePath) + 1 : 1); + } + } + + foreach (const QString &proFile, foundMains.keys()) { + TestConfiguration *tc = new TestConfiguration(QString(), QStringList(), + foundMains.value(proFile)); + tc->setProFile(proFile); + tc->setProject(project); + tc->setTestType(TestTypeGTest); + result << tc; + } + + return result; +} + +QList TestTreeModel::getSelectedTests() const +{ + QList result; + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (!project) + return result; + + TestConfiguration *testConfiguration = 0; + + for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) { + const TestTreeItem *child = m_autoTestRootItem->childItem(row); + + switch (child->checked()) { + case Qt::Unchecked: + continue; + case Qt::Checked: + testConfiguration = new TestConfiguration(child->name(), QStringList(), child->childCount()); + testConfiguration->setMainFilePath(child->filePath()); + testConfiguration->setProject(project); + result << testConfiguration; + continue; + case Qt::PartiallyChecked: + default: + const QString childName = child->name(); + int grandChildCount = child->childCount(); + QStringList testCases; + for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) { + const TestTreeItem *grandChild = child->childItem(grandChildRow); + if (grandChild->checked() == Qt::Checked) + testCases << grandChild->name(); + } + + testConfiguration = new TestConfiguration(childName, testCases); + testConfiguration->setMainFilePath(child->filePath()); + testConfiguration->setProject(project); + result << testConfiguration; + } + } + // Quick Tests must be handled differently - need the calling cpp file to use this in + // addProjectInformation() - additionally this must be unique to not execute the same executable + // on and on and on... + // TODO: do this later on for Auto Tests as well to support strange setups? or redo the model + + QMap foundMains; + + if (TestTreeItem *unnamed = unnamedQuickTests()) { + for (int childRow = 0, ccount = unnamed->childCount(); childRow < ccount; ++ childRow) { + const TestTreeItem *grandChild = unnamed->childItem(childRow); + const QString mainFile = grandChild->mainFile(); + if (foundMains.contains(mainFile)) { + QTC_ASSERT(testConfiguration, + qWarning() << "Illegal state (unnamed Quick Test listed as named)"; + return QList()); + foundMains[mainFile]->setTestCaseCount(testConfiguration->testCaseCount() + 1); + } else { + testConfiguration = new TestConfiguration(QString(), QStringList()); + testConfiguration->setTestCaseCount(1); + testConfiguration->setUnnamedOnly(true); + testConfiguration->setMainFilePath(mainFile); + testConfiguration->setProject(project); + foundMains.insert(mainFile, testConfiguration); + } + } + } + + for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) { + const TestTreeItem *child = m_quickTestRootItem->childItem(row); + // unnamed Quick Tests have been handled separately already + if (child->name().isEmpty()) + continue; + + // named Quick Tests + switch (child->checked()) { + case Qt::Unchecked: + continue; + case Qt::Checked: + case Qt::PartiallyChecked: + default: + QStringList testFunctions; + int grandChildCount = child->childCount(); + for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) { + const TestTreeItem *grandChild = child->childItem(grandChildRow); + if (grandChild->type() != TestTreeItem::TestFunction) + continue; + testFunctions << child->name() + QLatin1String("::") + grandChild->name(); + } + TestConfiguration *tc; + if (foundMains.contains(child->mainFile())) { + tc = foundMains[child->mainFile()]; + QStringList oldFunctions(tc->testCases()); + // if oldFunctions.size() is 0 this test configuration is used for at least one + // unnamed test case + if (oldFunctions.size() == 0) { + tc->setTestCaseCount(tc->testCaseCount() + testFunctions.size()); + tc->setUnnamedOnly(false); + } else { + oldFunctions << testFunctions; + tc->setTestCases(oldFunctions); + } + } else { + tc = new TestConfiguration(QString(), testFunctions); + tc->setMainFilePath(child->mainFile()); + tc->setProject(project); + foundMains.insert(child->mainFile(), tc); + } + break; + } + } + + foreach (TestConfiguration *config, foundMains.values()) + if (!config->unnamedOnly()) + result << config; + + // get selected Google Tests + QMap proFilesWithEnabledTestSets; + + for (int row = 0, count = m_googleTestRootItem->childCount(); row < count; ++row) { + const TestTreeItem *child = m_googleTestRootItem->childItem(row); + if (child->checked() == Qt::Unchecked) // add this test name to disabled list ? + continue; + + int grandChildCount = child->childCount(); + for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) { + const TestTreeItem *grandChild = child->childItem(grandChildRow); + const QString &proFile = grandChild->mainFile(); + QStringList enabled = proFilesWithEnabledTestSets.value(proFile); + if (grandChild->checked() == Qt::Checked) { + QString testSpecifier = child->name() + QLatin1Char('.') + grandChild->name(); + if (child->type() == TestTreeItem::GTestCaseParameterized) { + testSpecifier.prepend(QLatin1String("*/")); + testSpecifier.append(QLatin1String("/*")); + } + enabled << testSpecifier; + } + proFilesWithEnabledTestSets.insert(proFile, enabled); + } + } + + foreach (const QString &proFile, proFilesWithEnabledTestSets.keys()) { + TestConfiguration *tc = new TestConfiguration(QString(), + proFilesWithEnabledTestSets.value(proFile)); + tc->setTestType(TestTypeGTest); + tc->setProFile(proFile); + tc->setProject(project); + result << tc; + } + + return result; +} + +TestConfiguration *TestTreeModel::getTestConfiguration(const TestTreeItem *item) const +{ + QTC_ASSERT(item != 0, return 0); + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + QTC_ASSERT(project, return 0); + + TestConfiguration *config = 0; + switch (item->type()) { + case TestTreeItem::TestClass: { + if (item->parent() == m_quickTestRootItem) { + // Quick Test TestCase + QStringList testFunctions; + for (int row = 0, count = item->childCount(); row < count; ++row) { + testFunctions << item->name() + QLatin1String("::") + + item->childItem(row)->name(); + } + config = new TestConfiguration(QString(), testFunctions); + config->setMainFilePath(item->mainFile()); + config->setProject(project); + } else { + // normal auto test + config = new TestConfiguration(item->name(), QStringList(), item->childCount()); + config->setMainFilePath(item->filePath()); + config->setProject(project); + } + break; + } + case TestTreeItem::TestFunction: { + const TestTreeItem *parent = item->parentItem(); + if (parent->parent() == m_quickTestRootItem) { + // it's a Quick Test function of a named TestCase + QStringList testFunction(parent->name() + QLatin1String("::") + item->name()); + config = new TestConfiguration(QString(), testFunction); + config->setMainFilePath(parent->mainFile()); + config->setProject(project); + } else { + // normal auto test + config = new TestConfiguration(parent->name(), QStringList() << item->name()); + config->setMainFilePath(parent->filePath()); + config->setProject(project); + } + break; + } + case TestTreeItem::TestDataTag: { + const TestTreeItem *function = item->parentItem(); + const TestTreeItem *parent = function ? function->parentItem() : 0; + if (!parent) + return 0; + const QString functionWithTag = function->name() + QLatin1Char(':') + item->name(); + config = new TestConfiguration(parent->name(), QStringList() << functionWithTag); + config->setMainFilePath(parent->filePath()); + config->setProject(project); + break; + } + case TestTreeItem::GTestCase: + case TestTreeItem::GTestCaseParameterized: { + QString testSpecifier = item->name() + QLatin1String(".*"); + if (item->type() == TestTreeItem::GTestCaseParameterized) + testSpecifier.prepend(QLatin1String("*/")); + + if (int childCount = item->childCount()) { + config = new TestConfiguration(QString(), QStringList(testSpecifier)); + config->setTestCaseCount(childCount); + config->setProFile(item->childItem(0)->mainFile()); + config->setProject(project); + config->setTestType(TestTypeGTest); + } + break; + } + case TestTreeItem::GTestName: { + const TestTreeItem *parent = item->parentItem(); + QString testSpecifier = parent->name() + QLatin1Char('.') + item->name(); + + if (parent->type() == TestTreeItem::GTestCaseParameterized) { + testSpecifier.prepend(QLatin1String("*/")); + testSpecifier.append(QLatin1String("/*")); + } + config = new TestConfiguration(QString(), QStringList(testSpecifier)); + config->setProFile(item->mainFile()); + config->setProject(project); + config->setTestType(TestTypeGTest); + break; + } + // not supported items + default: + return 0; + } + return config; +} + +QString TestTreeModel::getMainFileForUnnamedQuickTest(const QString &qmlFile) const +{ + const TestTreeItem *unnamed = unnamedQuickTests(); + const int count = unnamed ? unnamed->childCount() : 0; + for (int row = 0; row < count; ++row) { + const TestTreeItem *child = unnamed->childItem(row); + if (qmlFile == child->filePath()) + return child->mainFile(); + } + return QString(); +} + +void TestTreeModel::qmlFilesForMainFile(const QString &mainFile, QSet *filePaths) const +{ + const TestTreeItem *unnamed = unnamedQuickTests(); + if (!unnamed) + return; + for (int row = 0, count = unnamed->childCount(); row < count; ++row) { + const TestTreeItem *child = unnamed->childItem(row); + if (child->mainFile() == mainFile) + filePaths->insert(child->filePath()); + } +} + +QList TestTreeModel::getUnnamedQuickTestFunctions() const +{ + const TestTreeItem *unnamed = unnamedQuickTests(); + if (unnamed) + return unnamed->getChildNames(); + return QList(); +} + +bool TestTreeModel::hasUnnamedQuickTests() const +{ + for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) + if (m_quickTestRootItem->childItem(row)->name().isEmpty()) + return true; + return false; +} + +TestTreeItem *TestTreeModel::unnamedQuickTests() const +{ + for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) { + TestTreeItem *child = m_quickTestRootItem->childItem(row); + if (child->name().isEmpty()) + return child; + } + return 0; +} + +void TestTreeModel::removeUnnamedQuickTests(const QString &filePath) +{ + TestTreeItem *unnamedQT = unnamedQuickTests(); + if (!unnamedQT) + return; + + for (int childRow = unnamedQT->childCount() - 1; childRow >= 0; --childRow) { + TestTreeItem *child = unnamedQT->childItem(childRow); + if (filePath == child->filePath()) + delete takeItem(child); + } + + if (unnamedQT->childCount() == 0) + delete takeItem(unnamedQT); + emit testTreeModelChanged(); +} + +void TestTreeModel::removeGTests(const QString &filePath) +{ + for (int childRow = m_googleTestRootItem->childCount() - 1; childRow >= 0; --childRow) { + TestTreeItem *child = m_googleTestRootItem->childItem(childRow); + for (int grandChildRow = child->childCount() - 1; grandChildRow >= 0; --grandChildRow) { + TestTreeItem *grandChild = child->childItem(grandChildRow); + if (filePath == grandChild->filePath()) + delete takeItem(grandChild); + } + if (child->childCount() == 0) + delete takeItem(child); + } + emit testTreeModelChanged(); +} + +void TestTreeModel::addTestTreeItem(TestTreeItem *item, TestTreeModel::Type type) +{ + TestTreeItem *parent = rootItemForType(type); + if (type == TestTreeModel::GoogleTest) { + // check if there's already an item with the same test name... + TestTreeItem *toBeUpdated = 0; + for (int row = 0, count = parent->childCount(); row < count; ++row) { + TestTreeItem *current = parent->childItem(row); + if (current->name() == item->name() && current->type() == item->type()) { + toBeUpdated = current; + break; + } + } + // ...if so we have, to update this one instead of adding a new item + if (toBeUpdated) { + for (int row = 0, count = item->childCount(); row < count; ++row) + toBeUpdated->appendChild(new TestTreeItem(*item->childItem(row))); + delete item; + } else { + parent->appendChild(item); + } + } else { + parent->appendChild(item); + } + emit testTreeModelChanged(); +} + +void TestTreeModel::updateUnnamedQuickTest(const QString &mainFile, + const QMap &functions) +{ + if (functions.isEmpty()) + return; + + if (!hasUnnamedQuickTests()) + addTestTreeItem(new TestTreeItem(QString(), QString(), TestTreeItem::TestClass), QuickTest); + + TestTreeItem *unnamed = unnamedQuickTests(); + foreach (const QString &functionName, functions.keys()) { + const TestCodeLocationAndType locationAndType = functions.value(functionName); + TestTreeItem *testFunction = new TestTreeItem(functionName, locationAndType.m_name, + locationAndType.m_type); + testFunction->setLine(locationAndType.m_line); + testFunction->setColumn(locationAndType.m_column); + testFunction->setMainFile(mainFile); + unnamed->appendChild(testFunction); + } +} + +void TestTreeModel::modifyTestTreeItem(TestTreeItem *item, TestTreeModel::Type type, const QStringList &files) +{ + QModelIndex index = rootIndexForType(type); + TestTreeItem *parent = rootItemForType(type); + if (files.isEmpty()) { + if (TestTreeItem *unnamed = unnamedQuickTests()) { + if (unnamed == item) // no need to update or delete + return; + + index = indexForItem(unnamed); + modifyTestSubtree(index, item); + } + } else { + for (int row = 0; row < parent->childCount(); ++row) { + if (files.contains(parent->childItem(row)->filePath())) { + index = index.child(row, 0); + modifyTestSubtree(index, item); + break; + } + } + } + // item was created as temporary, destroy it if it won't get destroyed by its parent + if (!item->parent()) + delete item; +} + +void TestTreeModel::removeAllTestItems() +{ + m_autoTestRootItem->removeChildren(); + m_quickTestRootItem->removeChildren(); + m_googleTestRootItem->removeChildren(); + emit testTreeModelChanged(); +} + +void TestTreeModel::removeTestTreeItems(const QString &filePath, Type type) +{ + bool removed = false; + const TestTreeItem *rootItem = rootItemForType(type); + for (int row = rootItem->childCount() - 1; row >= 0; --row) { + TestTreeItem *childItem = rootItem->childItem(row); + if (filePath == childItem->filePath()) { + delete takeItem(childItem); + removed = true; + } + } + if (removed) + emit testTreeModelChanged(); +} + +TestTreeItem *TestTreeModel::rootItemForType(TestTreeModel::Type type) +{ + switch (type) { + case AutoTest: + return m_autoTestRootItem; + case QuickTest: + return m_quickTestRootItem; + case GoogleTest: + return m_googleTestRootItem; + } + QTC_ASSERT(false, return 0); +} + +QModelIndex TestTreeModel::rootIndexForType(TestTreeModel::Type type) +{ + switch (type) { + case AutoTest: + return index(0, 0); + case QuickTest: + return index(1, 0); + case GoogleTest: + return index(2, 0); + } + QTC_ASSERT(false, return QModelIndex()); +} + +void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, const TestTreeItem *newItem) +{ + if (!toBeModifiedIndex.isValid()) + return; + + TestTreeItem *toBeModifiedItem = static_cast(itemForIndex(toBeModifiedIndex)); + if (toBeModifiedItem->modifyContent(newItem)) + emit dataChanged(toBeModifiedIndex, toBeModifiedIndex, + QVector() << Qt::DisplayRole << Qt::ToolTipRole << LinkRole); + + // process sub-items as well... + const int childCount = toBeModifiedItem->childCount(); + const int newChildCount = newItem->childCount(); + + // for keeping the CheckState on modifications + // TODO might still fail for duplicate entries + QHash checkStates; + for (int row = 0; row < childCount; ++row) { + const TestTreeItem *child = toBeModifiedItem->childItem(row); + checkStates.insert(child->name(), child->checked()); + } + + if (childCount <= newChildCount) { + processChildren(toBeModifiedIndex, newItem, childCount, checkStates); + // add additional items + for (int row = childCount; row < newChildCount; ++row) { + const TestTreeItem *newChild = newItem->childItem(row); + TestTreeItem *toBeAdded = new TestTreeItem(*newChild); + if (checkStates.contains(toBeAdded->name()) + && checkStates.value(toBeAdded->name()) != Qt::Checked) + toBeAdded->setChecked(checkStates.value(toBeAdded->name())); + toBeModifiedItem->appendChild(toBeAdded); + } + } else { + processChildren(toBeModifiedIndex, newItem, newChildCount, checkStates); + // remove rest of the items + for (int row = childCount - 1; row > newChildCount; --row) + delete takeItem(toBeModifiedItem->childItem(row)); + } + emit testTreeModelChanged(); +} + +void TestTreeModel::processChildren(QModelIndex &parentIndex, const TestTreeItem *newItem, + const int upperBound, + const QHash &checkStates) +{ + static QVector modificationRoles = QVector() << Qt::DisplayRole + << Qt::ToolTipRole + << LinkRole; + TestTreeItem *toBeModifiedItem = static_cast(itemForIndex(parentIndex)); + for (int row = 0; row < upperBound; ++row) { + QModelIndex child = parentIndex.child(row, 0); + TestTreeItem *toBeModifiedChild = toBeModifiedItem->childItem(row); + const TestTreeItem *modifiedChild = newItem->childItem(row); + if (toBeModifiedChild->modifyContent(modifiedChild)) + emit dataChanged(child, child, modificationRoles); + + // handle data tags - just remove old and add them + if (modifiedChild->childCount() || toBeModifiedChild->childCount()) { + toBeModifiedChild->removeChildren(); + const int count = modifiedChild->childCount(); + for (int childRow = 0; childRow < count; ++childRow) + toBeModifiedChild->appendChild(new TestTreeItem(*modifiedChild->childItem(childRow))); + } + + if (checkStates.contains(toBeModifiedChild->name())) { + Qt::CheckState state = checkStates.value(toBeModifiedChild->name()); + if (state != toBeModifiedChild->checked()) { + toBeModifiedChild->setChecked(state); + emit dataChanged(child, child, QVector() << Qt::CheckStateRole); + } + } else { // newly added (BAD: happens for renaming as well) + toBeModifiedChild->setChecked(Qt::Checked); + emit dataChanged(child, child, QVector() << Qt::CheckStateRole); + } + } +} + +#ifdef WITH_TESTS +int TestTreeModel::autoTestsCount() const +{ + return m_autoTestRootItem ? m_autoTestRootItem->childCount() : 0; +} + +int TestTreeModel::namedQuickTestsCount() const +{ + return m_quickTestRootItem + ? m_quickTestRootItem->childCount() - (hasUnnamedQuickTests() ? 1 : 0) + : 0; +} + +int TestTreeModel::unnamedQuickTestsCount() const +{ + if (TestTreeItem *unnamed = unnamedQuickTests()) + return unnamed->childCount(); + return 0; +} + +int TestTreeModel::dataTagsCount() const +{ + int dataTagCount = 0; + foreach (Utils::TreeItem *item, m_autoTestRootItem->children()) { + TestTreeItem *classItem = static_cast(item); + foreach (Utils::TreeItem *functionItem, classItem->children()) + dataTagCount += functionItem->childCount(); + } + return dataTagCount; +} + +int TestTreeModel::gtestNamesCount() const +{ + return m_googleTestRootItem ? m_googleTestRootItem->childCount() : 0; +} + +QMultiMap TestTreeModel::gtestNamesAndSets() const +{ + QMultiMap result; + + if (m_googleTestRootItem) { + for (int row = 0, count = m_googleTestRootItem->childCount(); row < count; ++row) { + const TestTreeItem *current = m_googleTestRootItem->childItem(row); + result.insert(current->name(), current->childCount()); + } + } + return result; +} +#endif + +/***************************** Sort/Filter Model **********************************/ + +TestTreeSortFilterModel::TestTreeSortFilterModel(TestTreeModel *sourceModel, QObject *parent) + : QSortFilterProxyModel(parent), + m_sourceModel(sourceModel), + m_sortMode(Alphabetically), + m_filterMode(Basic) +{ + setSourceModel(sourceModel); +} + +void TestTreeSortFilterModel::setSortMode(SortMode sortMode) +{ + m_sortMode = sortMode; + invalidate(); +} + +void TestTreeSortFilterModel::setFilterMode(FilterMode filterMode) +{ + m_filterMode = filterMode; + invalidateFilter(); +} + +void TestTreeSortFilterModel::toggleFilter(FilterMode filterMode) +{ + m_filterMode = toFilterMode(m_filterMode ^ filterMode); + invalidateFilter(); +} + +TestTreeSortFilterModel::FilterMode TestTreeSortFilterModel::toFilterMode(int f) +{ + switch (f) { + case TestTreeSortFilterModel::ShowInitAndCleanup: + return TestTreeSortFilterModel::ShowInitAndCleanup; + case TestTreeSortFilterModel::ShowTestData: + return TestTreeSortFilterModel::ShowTestData; + case TestTreeSortFilterModel::ShowAll: + return TestTreeSortFilterModel::ShowAll; + default: + return TestTreeSortFilterModel::Basic; + } +} + +bool TestTreeSortFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + // root items keep the intended order: 1st Auto Tests, 2nd Quick Tests + const TestTreeItem *leftItem = static_cast(left.internalPointer()); + if (leftItem->type() == TestTreeItem::Root) + return left.row() > right.row(); + + const QString leftVal = m_sourceModel->data(left, Qt::DisplayRole).toString(); + const QString rightVal = m_sourceModel->data(right, Qt::DisplayRole).toString(); + + // unnamed Quick Tests will always be listed first + if (leftVal == tr(Constants::UNNAMED_QUICKTESTS)) + return false; + if (rightVal == tr(Constants::UNNAMED_QUICKTESTS)) + return true; + + switch (m_sortMode) { + case Alphabetically: + if (leftVal == rightVal) + return left.row() > right.row(); + return leftVal > rightVal; + case Naturally: { + const TextEditor::TextEditorWidget::Link leftLink = + m_sourceModel->data(left, LinkRole).value(); + const TextEditor::TextEditorWidget::Link rightLink = + m_sourceModel->data(right, LinkRole).value(); + + if (leftLink.targetFileName == rightLink.targetFileName) { + return leftLink.targetLine == rightLink.targetLine + ? leftLink.targetColumn > rightLink.targetColumn + : leftLink.targetLine > rightLink.targetLine; + } else { + return leftLink.targetFileName > rightLink.targetFileName; + } + } + default: + return true; + } +} + +bool TestTreeSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex index = m_sourceModel->index(sourceRow, 0,sourceParent); + if (!index.isValid()) + return false; + + const TestTreeItem *item = static_cast(index.internalPointer()); + + switch (item->type()) { + case TestTreeItem::TestDataFunction: + return m_filterMode & ShowTestData; + case TestTreeItem::TestSpecialFunction: + return m_filterMode & ShowInitAndCleanup; + default: + return true; + } +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testtreemodel.h b/src/plugins/autotest/testtreemodel.h new file mode 100644 index 00000000000..b63c29c5a62 --- /dev/null +++ b/src/plugins/autotest/testtreemodel.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTTREEMODEL_H +#define TESTTREEMODEL_H + +#include "testconfiguration.h" + +#include + +#include + +#include + +namespace Autotest { +namespace Internal { + +struct TestCodeLocationAndType; +class TestCodeParser; +class TestInfo; +class TestTreeItem; + +class TestTreeModel : public Utils::TreeModel +{ + Q_OBJECT +public: + enum Type { + AutoTest, + QuickTest, + GoogleTest + }; + + static TestTreeModel* instance(); + ~TestTreeModel(); + void enableParsing(); + void disableParsing(); + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + + TestCodeParser *parser() const { return m_parser; } + bool hasTests() const; + QList getAllTestCases() const; + QList getSelectedTests() const; + TestConfiguration *getTestConfiguration(const TestTreeItem *item) const; + QString getMainFileForUnnamedQuickTest(const QString &qmlFile) const; + void qmlFilesForMainFile(const QString &mainFile, QSet *filePaths) const; + QList getUnnamedQuickTestFunctions() const; + bool hasUnnamedQuickTests() const; + +#ifdef WITH_TESTS + int autoTestsCount() const; + int namedQuickTestsCount() const; + int unnamedQuickTestsCount() const; + int dataTagsCount() const; + int gtestNamesCount() const; + QMultiMap gtestNamesAndSets() const; +#endif + +signals: + void testTreeModelChanged(); + +public slots: + +private: + void addTestTreeItem(TestTreeItem *item, Type type); + void updateUnnamedQuickTest(const QString &mainFile, + const QMap &functions); + void modifyTestTreeItem(TestTreeItem *item, Type type, const QStringList &file); + void removeAllTestItems(); + void removeTestTreeItems(const QString &filePath, Type type); + void removeUnnamedQuickTests(const QString &filePath); + void removeGTests(const QString &filePath); + + TestTreeItem *unnamedQuickTests() const; + TestTreeItem *rootItemForType(Type type); + QModelIndex rootIndexForType(Type type); + + explicit TestTreeModel(QObject *parent = 0); + void modifyTestSubtree(QModelIndex &toBeModifiedIndex, const TestTreeItem *newItem); + void processChildren(QModelIndex &parentIndex, const TestTreeItem *newItem, + const int upperBound, const QHash &checkStates); + + TestTreeItem *m_autoTestRootItem; + TestTreeItem *m_quickTestRootItem; + TestTreeItem *m_googleTestRootItem; + TestCodeParser *m_parser; + bool m_connectionsInitialized; + QAtomicInt m_refCounter; +}; + +class TestTreeSortFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + enum SortMode { + Alphabetically, + Naturally + }; + + enum FilterMode { + Basic, + ShowInitAndCleanup = 0x01, + ShowTestData = 0x02, + ShowAll = ShowInitAndCleanup | ShowTestData + }; + + TestTreeSortFilterModel(TestTreeModel *sourceModel, QObject *parent = 0); + void setSortMode(SortMode sortMode); + void setFilterMode(FilterMode filterMode); + void toggleFilter(FilterMode filterMode); + static FilterMode toFilterMode(int f); + +protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + +private: + TestTreeModel *m_sourceModel; + SortMode m_sortMode; + FilterMode m_filterMode; + +}; + +} // namespace Internal +} // namespace Autotest + +Q_DECLARE_METATYPE(Autotest::Internal::TestTreeModel::Type) + +#endif // TESTTREEMODEL_H diff --git a/src/plugins/autotest/testtreeview.cpp b/src/plugins/autotest/testtreeview.cpp new file mode 100644 index 00000000000..b6c8541450a --- /dev/null +++ b/src/plugins/autotest/testtreeview.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "autotestconstants.h" +#include "testcodeparser.h" +#include "testrunner.h" +#include "testtreeitem.h" +#include "testtreeitemdelegate.h" +#include "testtreemodel.h" +#include "testtreeview.h" + +#include +#include + +#include + +#include + +#include +#include + +#include + +#include + +#include +#include + +namespace Autotest { +namespace Internal { + +TestTreeView::TestTreeView(QWidget *parent) + : NavigationTreeView(parent), + m_context(new Core::IContext(this)) +{ + setExpandsOnDoubleClick(false); + m_context->setWidget(this); + m_context->setContext(Core::Context(Constants::AUTOTEST_CONTEXT)); + Core::ICore::addContextObject(m_context); +} + +void TestTreeView::selectAll() +{ + changeCheckStateAll(Qt::Checked); +} + +void TestTreeView::deselectAll() +{ + changeCheckStateAll(Qt::Unchecked); +} + +// this avoids the re-evaluation of parent nodes when modifying the child nodes (setData()) +void TestTreeView::changeCheckStateAll(const Qt::CheckState checkState) +{ + const TestTreeModel *model = TestTreeModel::instance(); + + // 3 == Auto Tests, Quick Tests and Google Tests - must be raised if there will be others + for (int rootRow = 0; rootRow < 3; ++rootRow) { + QModelIndex currentRootIndex = model->index(rootRow, 0, rootIndex()); + if (!currentRootIndex.isValid()) + return; + int count = model->rowCount(currentRootIndex); + QModelIndex last; + for (int classesRow = 0; classesRow < count; ++classesRow) { + const QModelIndex classesIndex = model->index(classesRow, 0, currentRootIndex); + int funcCount = model->rowCount(classesIndex); + TestTreeItem *item = static_cast(classesIndex.internalPointer()); + if (item) { + item->setChecked(checkState); + if (!item->childCount()) + last = classesIndex; + } + for (int functionRow = 0; functionRow < funcCount; ++functionRow) { + last = model->index(functionRow, 0, classesIndex); + TestTreeItem *item = static_cast(last.internalPointer()); + if (item) + item->setChecked(checkState); + } + } + emit dataChanged(currentRootIndex, last); + } +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testtreeview.h b/src/plugins/autotest/testtreeview.h new file mode 100644 index 00000000000..a75c2fdaa7c --- /dev/null +++ b/src/plugins/autotest/testtreeview.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTTREEVIEW_H +#define TESTTREEVIEW_H + +#include + +namespace Core { +class IContext; +} + +namespace Autotest { +namespace Internal { + +class TestTreeView : public Utils::NavigationTreeView +{ + Q_OBJECT + +public: + TestTreeView(QWidget *parent = 0); + + void selectAll(); + void deselectAll(); + +private: + void changeCheckStateAll(const Qt::CheckState checkState); + Core::IContext *m_context; +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTTREEVIEW_H diff --git a/src/plugins/autotest/testvisitor.cpp b/src/plugins/autotest/testvisitor.cpp new file mode 100644 index 00000000000..40819dfccdc --- /dev/null +++ b/src/plugins/autotest/testvisitor.cpp @@ -0,0 +1,405 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "autotest_utils.h" +#include "testvisitor.h" + +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +namespace Autotest { +namespace Internal { + +// names of special functions (applies for QTest as well as Quick Tests) +static QList specialFunctions = QList() << QLatin1String("initTestCase") + << QLatin1String("cleanupTestCase") + << QLatin1String("init") + << QLatin1String("cleanup"); + +/************************** Cpp Test Symbol Visitor ***************************/ + +TestVisitor::TestVisitor(const QString &fullQualifiedClassName) + : m_className(fullQualifiedClassName) +{ +} + +TestVisitor::~TestVisitor() +{ +} + +bool TestVisitor::visit(CPlusPlus::Class *symbol) +{ + const CPlusPlus::Overview o; + CPlusPlus::LookupContext lc; + + unsigned count = symbol->memberCount(); + for (unsigned i = 0; i < count; ++i) { + CPlusPlus::Symbol *member = symbol->memberAt(i); + CPlusPlus::Type *type = member->type().type(); + + const QString className = o.prettyName(lc.fullyQualifiedName(member->enclosingClass())); + if (className != m_className) + continue; + + if (const auto func = type->asFunctionType()) { + if (func->isSlot() && member->isPrivate()) { + const QString name = o.prettyName(func->name()); + TestCodeLocationAndType locationAndType; + + CPlusPlus::Function *functionDefinition = m_symbolFinder.findMatchingDefinition( + func, CppTools::CppModelManager::instance()->snapshot(), true); + if (functionDefinition) { + locationAndType.m_name = QString::fromUtf8(functionDefinition->fileName()); + locationAndType.m_line = functionDefinition->line(); + locationAndType.m_column = functionDefinition->column() - 1; + } else { // if we cannot find the definition use declaration as fallback + locationAndType.m_name = QString::fromUtf8(member->fileName()); + locationAndType.m_line = member->line(); + locationAndType.m_column = member->column() - 1; + } + if (specialFunctions.contains(name)) + locationAndType.m_type = TestTreeItem::TestSpecialFunction; + else if (name.endsWith(QLatin1String("_data"))) + locationAndType.m_type = TestTreeItem::TestDataFunction; + else + locationAndType.m_type = TestTreeItem::TestFunction; + locationAndType.m_state = TestTreeItem::Enabled; + m_privSlots.insert(name, locationAndType); + } + } + } + return true; +} + +/**************************** Cpp Test AST Visitor ****************************/ + +TestAstVisitor::TestAstVisitor(CPlusPlus::Document::Ptr doc) + : ASTVisitor(doc->translationUnit()), + m_currentDoc(doc) +{ +} + +TestAstVisitor::~TestAstVisitor() +{ +} + +bool TestAstVisitor::visit(CPlusPlus::CallAST *ast) +{ + if (!m_currentScope || m_currentDoc.isNull()) + return false; + + if (const auto expressionAST = ast->base_expression) { + if (const auto idExpressionAST = expressionAST->asIdExpression()) { + if (const auto qualifiedNameAST = idExpressionAST->name->asQualifiedName()) { + const CPlusPlus::Overview o; + const QString prettyName = o.prettyName(qualifiedNameAST->name); + if (prettyName == QLatin1String("QTest::qExec")) { + if (const auto expressionListAST = ast->expression_list) { + // first argument is the one we need + if (const auto argumentExpressionAST = expressionListAST->value) { + CPlusPlus::TypeOfExpression toe; + CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); + toe.init(m_currentDoc, cppMM->snapshot()); + QList toeItems + = toe(argumentExpressionAST, m_currentDoc, m_currentScope); + + if (toeItems.size()) { + if (const auto pointerType = toeItems.first().type()->asPointerType()) + m_className = o.prettyType(pointerType->elementType()); + } + } + } + } + } + } + } + return false; +} + +bool TestAstVisitor::visit(CPlusPlus::CompoundStatementAST *ast) +{ + m_currentScope = ast->symbol->asScope(); + return true; +} + +/********************** Test Data Function AST Visitor ************************/ + +TestDataFunctionVisitor::TestDataFunctionVisitor(CPlusPlus::Document::Ptr doc) + : CPlusPlus::ASTVisitor(doc->translationUnit()), + m_currentDoc(doc), + m_currentAstDepth(0), + m_insideUsingQTestDepth(0), + m_insideUsingQTest(false) +{ +} + +TestDataFunctionVisitor::~TestDataFunctionVisitor() +{ +} + +bool TestDataFunctionVisitor::visit(CPlusPlus::UsingDirectiveAST *ast) +{ + if (auto nameAST = ast->name) { + if (m_overview.prettyName(nameAST->name) == QLatin1String("QTest")) { + m_insideUsingQTest = true; + // we need the surrounding AST depth as using directive is an AST itself + m_insideUsingQTestDepth = m_currentAstDepth - 1; + } + } + return true; +} + +bool TestDataFunctionVisitor::visit(CPlusPlus::FunctionDefinitionAST *ast) +{ + if (ast->declarator) { + CPlusPlus::DeclaratorIdAST *id = ast->declarator->core_declarator->asDeclaratorId(); + if (!id || !ast->symbol || ast->symbol->argumentCount() != 0) + return false; + + CPlusPlus::LookupContext lc; + const QString prettyName = m_overview.prettyName(lc.fullyQualifiedName(ast->symbol)); + // do not handle functions that aren't real test data functions + if (!prettyName.endsWith(QLatin1String("_data"))) + return false; + + m_currentFunction = prettyName.left(prettyName.size() - 5); + m_currentTags.clear(); + return true; + } + + return false; +} + +bool TestDataFunctionVisitor::visit(CPlusPlus::CallAST *ast) +{ + if (m_currentFunction.isEmpty()) + return true; + + unsigned firstToken; + if (newRowCallFound(ast, &firstToken)) { + if (const auto expressionListAST = ast->expression_list) { + // first argument is the one we need + if (const auto argumentExpressionAST = expressionListAST->value) { + if (const auto stringLiteral = argumentExpressionAST->asStringLiteral()) { + auto token = m_currentDoc->translationUnit()->tokenAt( + stringLiteral->literal_token); + if (token.isStringLiteral()) { + unsigned line = 0; + unsigned column = 0; + m_currentDoc->translationUnit()->getTokenStartPosition( + firstToken, &line, &column); + TestCodeLocationAndType locationAndType; + locationAndType.m_name = QString::fromUtf8(token.spell()); + locationAndType.m_column = column - 1; + locationAndType.m_line = line; + locationAndType.m_type = TestTreeItem::TestDataTag; + locationAndType.m_state = TestTreeItem::Enabled; + m_currentTags.append(locationAndType); + } + } + } + } + } + return true; +} + +bool TestDataFunctionVisitor::preVisit(CPlusPlus::AST *) +{ + ++m_currentAstDepth; + return true; +} + +void TestDataFunctionVisitor::postVisit(CPlusPlus::AST *ast) +{ + --m_currentAstDepth; + m_insideUsingQTest &= m_currentAstDepth >= m_insideUsingQTestDepth; + + if (!ast->asFunctionDefinition()) + return; + + if (!m_currentFunction.isEmpty() && !m_currentTags.isEmpty()) + m_dataTags.insert(m_currentFunction, m_currentTags); + + m_currentFunction.clear(); + m_currentTags.clear(); +} + +bool TestDataFunctionVisitor::newRowCallFound(CPlusPlus::CallAST *ast, unsigned *firstToken) const +{ + QTC_ASSERT(firstToken, return false); + + if (!ast->base_expression) + return false; + + bool found = false; + + if (const CPlusPlus::IdExpressionAST *exp = ast->base_expression->asIdExpression()) { + if (!exp->name) + return false; + + if (const auto qualifiedNameAST = exp->name->asQualifiedName()) { + found = m_overview.prettyName(qualifiedNameAST->name) == QLatin1String("QTest::newRow"); + *firstToken = qualifiedNameAST->firstToken(); + } else if (m_insideUsingQTest) { + found = m_overview.prettyName(exp->name->name) == QLatin1String("newRow"); + *firstToken = exp->name->firstToken(); + } + } + return found; +} + +/*************************** Quick Test AST Visitor ***************************/ + +TestQmlVisitor::TestQmlVisitor(QmlJS::Document::Ptr doc) + : m_currentDoc(doc) +{ +} + +TestQmlVisitor::~TestQmlVisitor() +{ +} + +bool TestQmlVisitor::visit(QmlJS::AST::UiObjectDefinition *ast) +{ + const QStringRef name = ast->qualifiedTypeNameId->name; + if (name != QLatin1String("TestCase")) + return true; // find nested TestCase items as well + + m_currentTestCaseName.clear(); + const auto sourceLocation = ast->firstSourceLocation(); + m_testCaseLocation.m_name = m_currentDoc->fileName(); + m_testCaseLocation.m_line = sourceLocation.startLine; + m_testCaseLocation.m_column = sourceLocation.startColumn - 1; + m_testCaseLocation.m_type = TestTreeItem::TestClass; + return true; +} + +bool TestQmlVisitor::visit(QmlJS::AST::ExpressionStatement *ast) +{ + const QmlJS::AST::ExpressionNode *expr = ast->expression; + return expr->kind == QmlJS::AST::Node::Kind_StringLiteral; +} + +bool TestQmlVisitor::visit(QmlJS::AST::UiScriptBinding *ast) +{ + const QStringRef name = ast->qualifiedId->name; + return name == QLatin1String("name"); +} + +bool TestQmlVisitor::visit(QmlJS::AST::FunctionDeclaration *ast) +{ + const QStringRef name = ast->name; + if (name.startsWith(QLatin1String("test_")) + || name.startsWith(QLatin1String("benchmark_")) + || name.endsWith(QLatin1String("_data")) + || specialFunctions.contains(name.toString())) { + const auto sourceLocation = ast->firstSourceLocation(); + TestCodeLocationAndType locationAndType; + locationAndType.m_name = m_currentDoc->fileName(); + locationAndType.m_line = sourceLocation.startLine; + locationAndType.m_column = sourceLocation.startColumn - 1; + if (specialFunctions.contains(name.toString())) + locationAndType.m_type = TestTreeItem::TestSpecialFunction; + else if (name.endsWith(QLatin1String("_data"))) + locationAndType.m_type = TestTreeItem::TestDataFunction; + else + locationAndType.m_type = TestTreeItem::TestFunction; + + locationAndType.m_state = TestTreeItem::Enabled; + m_testFunctions.insert(name.toString(), locationAndType); + } + return false; +} + +bool TestQmlVisitor::visit(QmlJS::AST::StringLiteral *ast) +{ + m_currentTestCaseName = ast->value.toString(); + return false; +} + +/********************** Google Test Function AST Visitor **********************/ + +GTestVisitor::GTestVisitor(CPlusPlus::Document::Ptr doc) + : CPlusPlus::ASTVisitor(doc->translationUnit()) + , m_document(doc) +{ +} + +bool GTestVisitor::visit(CPlusPlus::FunctionDefinitionAST *ast) +{ + static QString disabledPrefix = QString::fromLatin1("DISABLED_"); + if (!ast || !ast->declarator || !ast->declarator->core_declarator) + return false; + + CPlusPlus::DeclaratorIdAST *id = ast->declarator->core_declarator->asDeclaratorId(); + if (!id || !ast->symbol || ast->symbol->argumentCount() != 2) + return false; + + CPlusPlus::LookupContext lc; + const QString prettyName = m_overview.prettyName(lc.fullyQualifiedName(ast->symbol)); + if (!TestUtils::isGTestMacro(prettyName)) + return false; + + CPlusPlus::Argument *testCaseNameArg = ast->symbol->argumentAt(0)->asArgument(); + CPlusPlus::Argument *testNameArg = ast->symbol->argumentAt(1)->asArgument(); + if (testCaseNameArg && testNameArg) { + const QString &testCaseName = m_overview.prettyType(testCaseNameArg->type()); + const QString &testName = m_overview.prettyType(testNameArg->type()); + + const bool disabled = testName.startsWith(disabledPrefix); + const bool disabledCase = testCaseName.startsWith(disabledPrefix); + unsigned line = 0; + unsigned column = 0; + unsigned token = id->firstToken(); + m_document->translationUnit()->getTokenStartPosition(token, &line, &column); + + TestCodeLocationAndType locationAndType; + locationAndType.m_name = disabled ? testName.mid(9) : testName; + locationAndType.m_line = line; + locationAndType.m_column = column - 1; + locationAndType.m_type = TestTreeItem::GTestName; + locationAndType.m_state = (disabled || disabledCase) ? TestTreeItem::Disabled + : TestTreeItem::Enabled; + GTestCaseSpec spec; + spec.testCaseName = disabledCase ? testCaseName.mid(9) : testCaseName; + spec.parameterized = TestUtils::isGTestParameterized(prettyName); + m_gtestFunctions[spec].append(locationAndType); + } + + return false; +} + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/testvisitor.h b/src/plugins/autotest/testvisitor.h new file mode 100644 index 00000000000..b4fb28329f8 --- /dev/null +++ b/src/plugins/autotest/testvisitor.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef TESTVISITOR_H +#define TESTVISITOR_H + +#include "testtreeitem.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +namespace Autotest { +namespace Internal { + +class TestVisitor : public CPlusPlus::SymbolVisitor +{ +public: + TestVisitor(const QString &fullQualifiedClassName); + virtual ~TestVisitor(); + + QMap privateSlots() const { return m_privSlots; } + + bool visit(CPlusPlus::Class *symbol); + +private: + CppTools::SymbolFinder m_symbolFinder; + QString m_className; + QMap m_privSlots; +}; + +class TestAstVisitor : public CPlusPlus::ASTVisitor +{ +public: + TestAstVisitor(CPlusPlus::Document::Ptr doc); + virtual ~TestAstVisitor(); + + bool visit(CPlusPlus::CallAST *ast); + bool visit(CPlusPlus::CompoundStatementAST *ast); + + QString className() const { return m_className; } + +private: + QString m_className; + CPlusPlus::Scope *m_currentScope; + CPlusPlus::Document::Ptr m_currentDoc; + +}; + +class TestDataFunctionVisitor : public CPlusPlus::ASTVisitor +{ +public: + TestDataFunctionVisitor(CPlusPlus::Document::Ptr doc); + virtual ~TestDataFunctionVisitor(); + + bool visit(CPlusPlus::UsingDirectiveAST *ast); + bool visit(CPlusPlus::FunctionDefinitionAST *ast); + bool visit(CPlusPlus::CallAST *ast); + bool preVisit(CPlusPlus::AST *ast); + void postVisit(CPlusPlus::AST *ast); + QMap dataTags() const { return m_dataTags; } + +private: + bool newRowCallFound(CPlusPlus::CallAST *ast, unsigned *firstToken) const; + + CPlusPlus::Document::Ptr m_currentDoc; + CPlusPlus::Overview m_overview; + QString m_currentFunction; + QMap m_dataTags; + TestCodeLocationList m_currentTags; + unsigned m_currentAstDepth; + unsigned m_insideUsingQTestDepth; + bool m_insideUsingQTest; +}; + +class TestQmlVisitor : public QmlJS::AST::Visitor +{ +public: + TestQmlVisitor(QmlJS::Document::Ptr doc); + virtual ~TestQmlVisitor(); + + bool visit(QmlJS::AST::UiObjectDefinition *ast); + bool visit(QmlJS::AST::ExpressionStatement *ast); + bool visit(QmlJS::AST::UiScriptBinding *ast); + bool visit(QmlJS::AST::FunctionDeclaration *ast); + bool visit(QmlJS::AST::StringLiteral *ast); + + QString testCaseName() const { return m_currentTestCaseName; } + TestCodeLocationAndType testCaseLocation() const { return m_testCaseLocation; } + QMap testFunctions() const { return m_testFunctions; } + +private: + QmlJS::Document::Ptr m_currentDoc; + QString m_currentTestCaseName; + TestCodeLocationAndType m_testCaseLocation; + QMap m_testFunctions; + +}; + +struct GTestCaseSpec +{ + QString testCaseName; + bool parameterized; +}; + +inline bool operator<(const GTestCaseSpec &spec1, const GTestCaseSpec &spec2) +{ + if (spec1.testCaseName != spec2.testCaseName) + return spec1.testCaseName < spec2.testCaseName; + return spec1.parameterized == spec2.parameterized ? false : !spec1.parameterized; +} + +class GTestVisitor : public CPlusPlus::ASTVisitor +{ +public: + GTestVisitor(CPlusPlus::Document::Ptr doc); + bool visit(CPlusPlus::FunctionDefinitionAST *ast); + + QMap gtestFunctions() const { return m_gtestFunctions; } + +private: + CPlusPlus::Document::Ptr m_document; + CPlusPlus::Overview m_overview; + QMap m_gtestFunctions; + +}; + +} // namespace Internal +} // namespace Autotest + +#endif // TESTVISITOR_H diff --git a/src/plugins/autotest/unit_test/mixed_atp/mixed_atp.pro b/src/plugins/autotest/unit_test/mixed_atp/mixed_atp.pro new file mode 100644 index 00000000000..068ea5a3a64 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/mixed_atp.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +SUBDIRS += src \ + tests + diff --git a/src/plugins/autotest/unit_test/mixed_atp/mixed_atp.qbs b/src/plugins/autotest/unit_test/mixed_atp/mixed_atp.qbs new file mode 100644 index 00000000000..dfc99c15df3 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/mixed_atp.qbs @@ -0,0 +1,8 @@ +import qbs + +Project { + references: [ + "src/src.qbs", + "tests/tests.qbs" + ] +} diff --git a/src/plugins/autotest/unit_test/mixed_atp/src/main.cpp b/src/plugins/autotest/unit_test/mixed_atp/src/main.cpp new file mode 100644 index 00000000000..e0c10bca029 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/src/main.cpp @@ -0,0 +1,8 @@ +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + return a.exec(); +} + diff --git a/src/plugins/autotest/unit_test/mixed_atp/src/src.pro b/src/plugins/autotest/unit_test/mixed_atp/src/src.pro new file mode 100644 index 00000000000..9e554af8b71 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/src/src.pro @@ -0,0 +1,6 @@ +QT += gui widgets + +TEMPLATE = app + +SOURCES += main.cpp + diff --git a/src/plugins/autotest/unit_test/mixed_atp/src/src.qbs b/src/plugins/autotest/unit_test/mixed_atp/src/src.qbs new file mode 100644 index 00000000000..5ed4dd4a364 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/src/src.qbs @@ -0,0 +1,11 @@ +import qbs + +CppApplication { + type: "application" + name: "Dummy Application" + + Depends { name: "Qt.gui" } + Depends { name: "Qt.widgets" } + + files: [ "main.cpp" ] +} diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/auto.pro b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/auto.pro new file mode 100644 index 00000000000..cb4dc9ec327 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/auto.pro @@ -0,0 +1,15 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + bench \ + dummy \ + gui + +greaterThan(QT_MAJOR_VERSION, 4) { + message("enabling quick tests") + SUBDIRS += quickauto \ + quickauto2 +} else { + message("quick tests disabled") +} + diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/auto.qbs b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/auto.qbs new file mode 100644 index 00000000000..a27b3922443 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/auto.qbs @@ -0,0 +1,13 @@ +import qbs + +Project { + name: "Auto tests" + + references: [ + "bench/bench.qbs", + "dummy/dummy.qbs", + "gui/gui.qbs", + "quickauto/quickauto.qbs", + "quickauto2/quickauto2.qbs" + ] +} diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/bench/bench.pro b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/bench/bench.pro new file mode 100644 index 00000000000..87f391f41d7 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/bench/bench.pro @@ -0,0 +1,12 @@ +QT += testlib + +QT -= gui + +TARGET = tst_benchtest +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += tst_benchtest.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/bench/bench.qbs b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/bench/bench.qbs new file mode 100644 index 00000000000..d10891316a2 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/bench/bench.qbs @@ -0,0 +1,14 @@ +import qbs + +CppApplication { + type: "application" + name: "Benchmark Auto Test" + targetName: "tst_benchtest" + + Depends { name: "cpp" } + Depends { name: "Qt.test" } + + files: [ "tst_benchtest.cpp" ] + + cpp.defines: base.concat("SRCDIR=" + path) +} diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/bench/tst_benchtest.cpp b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/bench/tst_benchtest.cpp new file mode 100644 index 00000000000..2b200f254fb --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/bench/tst_benchtest.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 +#include + +class BenchTest : public QObject +{ + Q_OBJECT + +public: + BenchTest(); + +private Q_SLOTS: + void testCase1(); + void testCase1_data(); +}; + +BenchTest::BenchTest() +{ +} + +void BenchTest::testCase1() +{ + QFETCH(bool, localAware); + + QString str1 = QLatin1String("Hello World"); + QString str2 = QLatin1String("Hallo Welt"); + if (!localAware) { + QBENCHMARK { + str1 == str2; + } + } else { + QBENCHMARK { + str1.localeAwareCompare(str2) == 0; + } + } +} + +void BenchTest::testCase1_data() +{ + QTest::addColumn("localAware"); + QTest::newRow("localAware") << true; + QTest::newRow("simple") << false; +} + +QTEST_MAIN(BenchTest) + +#include "tst_benchtest.moc" diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/dummy.pro b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/dummy.pro new file mode 100644 index 00000000000..40e95bcc390 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/dummy.pro @@ -0,0 +1,10 @@ +QT += testlib +QT += gui +CONFIG += qt warn_on depend_includepath testcase +TEMPLATE = app + +TARGET = tst_FooBar + +HEADERS += tst_foo.h +SOURCES += tst_foo.cpp + diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/dummy.qbs b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/dummy.qbs new file mode 100644 index 00000000000..012ba11a673 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/dummy.qbs @@ -0,0 +1,12 @@ +import qbs + +CppApplication { + type: "application" + name: "Dummy auto test" + targetName: "tst_FooBar" + + Depends { name: "Qt.test" } + Depends { name: "Qt.gui" } + + files: [ "tst_foo.cpp", "tst_foo.h" ] +} diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/tst_foo.cpp b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/tst_foo.cpp new file mode 100644 index 00000000000..1646cf14500 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/tst_foo.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "tst_foo.h" +#include +#include +#include + +Foo::Foo() +{ +} + +Foo::~Foo() +{ +} + +void Foo::initTestCase() +{ +} + +void Foo::cleanupTestCase() +{ + QWARN("Warning!"); +} + +void Foo::test_caseZero() +{ + QCOMPARE(1, 2); +} + +void Foo::test_case1() +{ + qDebug() << "test_case1"; + QFETCH(int, val); + + QEXPECT_FAIL("test2", "2", Continue); + QCOMPARE(val, 1); + QEXPECT_FAIL("test1", "bla", Abort); + QCOMPARE(val, 2); + QVERIFY2(true, "Hallo"); +} + +void Foo::test_case1_data() +{ + QTest::addColumn("val"); + QTest::newRow("test1") << 1; + QTest::newRow("test2") << 2; + QTest::newRow("test3") << 3; + QTest::newRow("test4") << 4; +} + +void Foo::test_case2() +{ + QThread::sleep(1); + qDebug() << "test_case2 - all pass"; + QSKIP("Skip for now", SkipAll); + QCOMPARE(1 ,1); + QVERIFY(true); +} + +void Foo::test_case4() +{ + qDebug("äøæ"); + QSKIP("Skipping test_case4", SkipSingle); + QFAIL("bla"); +} + +QTEST_MAIN(Foo) diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/tst_foo.h b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/tst_foo.h new file mode 100644 index 00000000000..1f70867f807 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/dummy/tst_foo.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ +#ifndef FOO_H_INCLUDED +#define FOO_H_INCLUDED + +#include + +class Foo : public QObject +{ + Q_OBJECT + +public: + Foo(); + ~Foo(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void test_case1(); + void test_case1_data(); + void test_caseZero(); + void test_case2(); +// void test_case3() {} + void test_case4(); + void test_case5() {} +}; + +#endif diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/gui/gui.pro b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/gui/gui.pro new file mode 100644 index 00000000000..10f8f27eb84 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/gui/gui.pro @@ -0,0 +1,8 @@ +QT += testlib gui widgets + +TARGET = tst_guitest + +TEMPLATE = app + +SOURCES += tst_guitest.cpp +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/gui/gui.qbs b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/gui/gui.qbs new file mode 100644 index 00000000000..05359cf10fc --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/gui/gui.qbs @@ -0,0 +1,13 @@ +import qbs + +CppApplication { + name: "Gui auto test" + targetName: "tst_gui" + + Depends { name: "Qt"; submodules: [ "gui", "widgets", "test" ] } + Depends { name: "cpp" } + + files: [ "tst_guitest.cpp" ] + + cpp.defines: base.concat("SRCDIR=" + path) +} diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/gui/tst_guitest.cpp b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/gui/tst_guitest.cpp new file mode 100644 index 00000000000..1f7fce915b4 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/gui/tst_guitest.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 +#include +#include +#include + +class GuiTest : public QObject +{ + Q_OBJECT + +public: + GuiTest(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void testCase1(); + void testGui_data(); + void testGui(); +}; + +GuiTest::GuiTest() +{ +} + +void GuiTest::initTestCase() +{ +} + +void GuiTest::cleanupTestCase() +{ +} + +void GuiTest::testCase1() +{ + QLatin1String str("Hello World"); + QLineEdit lineEdit; + QTest::keyClicks(&lineEdit, str); + QCOMPARE(lineEdit.text(), str); +} + +void GuiTest::testGui() +{ + QFETCH(QTestEventList, events); + QFETCH(QString, expected); + QLineEdit lineEdit; + events.simulate(&lineEdit); + QCOMPARE(lineEdit.text(), expected); +} + +void GuiTest::testGui_data() +{ + QTest::addColumn("events"); + QTest::addColumn("expected"); + + QTestEventList list1; + list1.addKeyClick('a'); + QTest::newRow("char") << list1 << "a"; + + QTestEventList list2; + list2.addKeyClick('a'); + list2.addKeyClick(Qt::Key_Backspace); + QTest::newRow("there and back again") << list2 << ""; +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + GuiTest gt; + return QTest::qExec(>, argc, argv); +} + +#include "tst_guitest.moc" diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/TestDummy.qml b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/TestDummy.qml new file mode 100644 index 00000000000..ae30c811438 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/TestDummy.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0 +import QtTest 1.0 + +TestCase { + name: "HalloBallo" + function test_bla() { + verify(true, "verifying true"); + } +} + diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/bar/tst_foo.qml b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/bar/tst_foo.qml new file mode 100644 index 00000000000..0653fcda9c4 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/bar/tst_foo.qml @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ +import QtQuick 2.0 +import QtTest 1.0 + +TestCase { + name: "subdirTC" + + function test_blabla() { + compare(1, 2, "Bla"); + } + +} + diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/main.cpp b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/main.cpp new file mode 100644 index 00000000000..16a331e4cc4 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/main.cpp @@ -0,0 +1,3 @@ +#include + +QUICK_TEST_MAIN(blob) diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/notlisted/tst_bla.qml b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/notlisted/tst_bla.qml new file mode 100644 index 00000000000..52b3837818c --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/notlisted/tst_bla.qml @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ +import QtQuick 2.0 +import QtTest 1.0 + +TestCase { + name: "notlisted" + + function test_blablabla() { + compare(1, 2, "Blubb"); + } + +} + diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/quickauto.pro b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/quickauto.pro new file mode 100644 index 00000000000..dfe495dc1c5 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/quickauto.pro @@ -0,0 +1,14 @@ +TEMPLATE = app +TARGET = test_mal_qtquick + +CONFIG += warn_on qmltestcase + +DISTFILES += \ + tst_test1.qml \ + tst_test2.qml \ + TestDummy.qml \ + bar/tst_foo.qml \ + tst_test3.qml + +SOURCES += \ + main.cpp diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/quickauto.qbs b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/quickauto.qbs new file mode 100644 index 00000000000..1697cd51a29 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/quickauto.qbs @@ -0,0 +1,33 @@ +import qbs + +CppApplication { + name: "Qt Quick auto test" + targetName: "test_mal_qtquick" + + Depends { name: "cpp" } + Depends { name: "Qt.core" } + Depends { + condition: Qt.core.versionMajor > 4 + name: "Qt.qmltest" + } + + Group { + name: "main application" + condition: Qt.core.versionMajor > 4 + + files: [ "main.cpp" ] + } + + Group { + name: "qml test files" + qbs.install: true + + files: [ + "tst_test1.qml", "tst_test2.qml", "TestDummy.qml", + "bar/tst_foo.qml", "tst_test3.qml" + ] + } + + // this should be set automatically, but it seems as if this does not happen + cpp.defines: base.concat("QUICK_TEST_SOURCE_DIR=\"" + path + "\"") +} diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/tst_test1.qml b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/tst_test1.qml new file mode 100644 index 00000000000..fa609a9aced --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/tst_test1.qml @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ +import QtQuick 2.0 +import QtTest 1.0 + +TestCase { + name: "Banana" + + function test_math() { + compare(5 + 5, 10, "verifying 5 + 5 = 10"); + compare(10 - 5, 5, "verifying 10 - 5 = 5"); + } + + function test_fail() { + verify(false, "verifying false"); + } +} + diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/tst_test2.qml b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/tst_test2.qml new file mode 100644 index 00000000000..d0115c5dd74 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/tst_test2.qml @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ +import QtQuick 2.0 +import QtTest 1.0 + +TestCase { + function test_str() { + var bla = String(); + bla = bla.concat("Hallo", " ", "Welt"); + var blubb = String("Hallo Welt"); + compare(blubb, bla, "Comparing concat"); + verify(blubb == bla, "Comparing concat equality") + } + +// nested TestCases actually fail +// TestCase { +// name: "boo" + +// function test_boo() { +// verify(true); +// } + +// TestCase { +// name: "far" + +// function test_far() { +// verify(true); +// } +// } +// } +} + diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/tst_test3.qml b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/tst_test3.qml new file mode 100644 index 00000000000..f28af4f77e9 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto/tst_test3.qml @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ +import QtQuick 2.0 +import QtTest 1.0 + +TestCase { + function test_bolle() { + verify(true, "verifying true"); + } + + function test_str() { + compare("hallo", "hallo"); + } +} + diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/main.cpp b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/main.cpp new file mode 100644 index 00000000000..e496b3eb656 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/main.cpp @@ -0,0 +1,3 @@ +#include + +QUICK_TEST_MAIN(blob2) diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/quickauto2.pro b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/quickauto2.pro new file mode 100644 index 00000000000..61c76d1562c --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/quickauto2.pro @@ -0,0 +1,11 @@ +TEMPLATE = app +TARGET = test_mal_qtquick + +CONFIG += warn_on qmltestcase + +DISTFILES += \ + tst_test1.qml \ + tst_test2.qml + +SOURCES += \ + main.cpp diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/quickauto2.qbs b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/quickauto2.qbs new file mode 100644 index 00000000000..a5fe65fbd65 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/quickauto2.qbs @@ -0,0 +1,29 @@ +import qbs + +CppApplication { + name: "Qt Quick auto test 2" + targetName: "test_mal_qtquick" + + Depends { name: "cpp" } + Depends { name: "Qt.core" } + Depends { + condition: Qt.core.versionMajor > 4 + name: "Qt.qmltest" + } + + Group { + condition: Qt.core.versionMajor > 4 + name: "main application" + files: [ "main.cpp" ] + } + + Group { + name: "qml test files" + qbs.install: true + + files: [ "tst_test1.qml", "tst_test2.qml" ] + } + + // this should be set automatically, but it seems as if this does not happen + cpp.defines: base.concat("QUICK_TEST_SOURCE_DIR=\"" + path + "\"") +} diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/tst_test1.qml b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/tst_test1.qml new file mode 100644 index 00000000000..fa609a9aced --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/tst_test1.qml @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ +import QtQuick 2.0 +import QtTest 1.0 + +TestCase { + name: "Banana" + + function test_math() { + compare(5 + 5, 10, "verifying 5 + 5 = 10"); + compare(10 - 5, 5, "verifying 10 - 5 = 5"); + } + + function test_fail() { + verify(false, "verifying false"); + } +} + diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/tst_test2.qml b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/tst_test2.qml new file mode 100644 index 00000000000..8a7d4dd9b47 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/auto/quickauto2/tst_test2.qml @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ +import QtQuick 2.0 +import QtTest 1.0 + +Rectangle { + TestCase { + name: "nestedTC" + + function test_str() { + var bla = String(); + bla = bla.concat("Hello", " ", "World"); + var blubb = String("Hello World"); + compare(blubb, bla, "Comparing concat"); + verify(blubb == bla, "Comparing concat equality") + } + } +} diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/tests.pro b/src/plugins/autotest/unit_test/mixed_atp/tests/tests.pro new file mode 100644 index 00000000000..f1633f70c95 --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/tests.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs + +SUBDIRS += auto + diff --git a/src/plugins/autotest/unit_test/mixed_atp/tests/tests.qbs b/src/plugins/autotest/unit_test/mixed_atp/tests/tests.qbs new file mode 100644 index 00000000000..e62815789fc --- /dev/null +++ b/src/plugins/autotest/unit_test/mixed_atp/tests/tests.qbs @@ -0,0 +1,7 @@ +import qbs + +Project { + name: "Tests" + + references: [ "auto/auto.qbs" ] +} diff --git a/src/plugins/autotest/unit_test/plain/plain.pro b/src/plugins/autotest/unit_test/plain/plain.pro new file mode 100644 index 00000000000..07cbd339227 --- /dev/null +++ b/src/plugins/autotest/unit_test/plain/plain.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs + +SUBDIRS += test_plain + diff --git a/src/plugins/autotest/unit_test/plain/plain.qbs b/src/plugins/autotest/unit_test/plain/plain.qbs new file mode 100644 index 00000000000..c2fe33618a8 --- /dev/null +++ b/src/plugins/autotest/unit_test/plain/plain.qbs @@ -0,0 +1,7 @@ +import qbs + +Project { + name: "Plain test project" + + references: [ "test_plain/test_plain.qbs" ] +} diff --git a/src/plugins/autotest/unit_test/plain/test_plain/test_plain.pro b/src/plugins/autotest/unit_test/plain/test_plain/test_plain.pro new file mode 100644 index 00000000000..1e8994b34c5 --- /dev/null +++ b/src/plugins/autotest/unit_test/plain/test_plain/test_plain.pro @@ -0,0 +1,13 @@ +QT += testlib +QT += gui +CONFIG += qt warn_on depend_includepath testcase + +TEMPLATE = app + +SOURCES += \ + tst_simple.cpp + +HEADERS += \ + tst_simple.h + +TARGET = totallyDifferentName diff --git a/src/plugins/autotest/unit_test/plain/test_plain/test_plain.qbs b/src/plugins/autotest/unit_test/plain/test_plain/test_plain.qbs new file mode 100644 index 00000000000..1b7937ce6a4 --- /dev/null +++ b/src/plugins/autotest/unit_test/plain/test_plain/test_plain.qbs @@ -0,0 +1,10 @@ +import qbs + +CppApplication { + type: "application" // suppress bundle generation on OSX + + Depends { name: "Qt.gui" } + Depends { name: "Qt.test" } + + files: [ "tst_simple.cpp", "tst_simple.h" ] +} diff --git a/src/plugins/autotest/unit_test/plain/test_plain/tst_simple.cpp b/src/plugins/autotest/unit_test/plain/test_plain/tst_simple.cpp new file mode 100644 index 00000000000..6f7ee9f1386 --- /dev/null +++ b/src/plugins/autotest/unit_test/plain/test_plain/tst_simple.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "tst_simple.h" +#include + +namespace Foo { +namespace Bar { + +class bla : public QObject +{ + Q_OBJECT + +public: + bla() {} + ~bla() {} + +private slots: + void tst_dummy() {} + +}; + +class anotherBla : public QObject +{ + Q_OBJECT + +private slots: + void init() {} + void cleanup() {} + + void tst_bla() {} + void test_foo() {} + void test_bar() {} + void tst_batz() {} + void test_hello() {} + void test_io() { qDebug("ä");} +}; + +} // namespace Foo +} // namespace Bar + +int main(int argc, char* argv[]) +{ + Foo::Bar::anotherBla *x2 = new Foo::Bar::anotherBla; + int result = QTest::qExec(x2, argc, argv); +// multiple QTest::qExec() calls actually do not work +// result += QTest::qExec(new Foo::Bar::bla, argc, argv); + return result; +} + +#include "tst_simple.moc" diff --git a/src/plugins/autotest/unit_test/plain/test_plain/tst_simple.h b/src/plugins/autotest/unit_test/plain/test_plain/tst_simple.h new file mode 100644 index 00000000000..25523237cb9 --- /dev/null +++ b/src/plugins/autotest/unit_test/plain/test_plain/tst_simple.h @@ -0,0 +1,4 @@ +#ifndef TST_SIMPLE_H +#define TST_SIMPLE_H +#include +#endif // TST_SIMPLE_H diff --git a/src/plugins/autotest/unit_test/simple_gt/simple_gt.pro b/src/plugins/autotest/unit_test/simple_gt/simple_gt.pro new file mode 100644 index 00000000000..9ce665aceff --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/simple_gt.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +SUBDIRS += src \ + tests + diff --git a/src/plugins/autotest/unit_test/simple_gt/src/main.cpp b/src/plugins/autotest/unit_test/simple_gt/src/main.cpp new file mode 100644 index 00000000000..25579cd61bc --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/src/main.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char *argv[]) { + QCoreApplication a(argc, argv); + return a.exec(); +} diff --git a/src/plugins/autotest/unit_test/simple_gt/src/src.pro b/src/plugins/autotest/unit_test/simple_gt/src/src.pro new file mode 100644 index 00000000000..03886429244 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/src/src.pro @@ -0,0 +1,9 @@ +TEMPLATE = app + +CONFIG += qt console +CONFIG -= app_bundle + +QT += core + +SOURCES += main.cpp + diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt1/further.cpp b/src/plugins/autotest/unit_test/simple_gt/tests/gt1/further.cpp new file mode 100644 index 00000000000..977fda04da5 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt1/further.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 + +using namespace testing; + +int sum(int a, int b) +{ + return a + b; +} + +TEST(Sum, HandlePositives) +{ + EXPECT_EQ(5, sum(2, 3)); + EXPECT_EQ(5, sum(3, 2)); +} + +TEST(Sum, HandleZero) +{ + EXPECT_EQ(1, sum(0, 0)); +} + +TEST(FactorialTest, DISABLED_Fake) +{ + EXPECT_EQ(-4, sum(-2, -2)); +} diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt1/gt1.pro b/src/plugins/autotest/unit_test/simple_gt/tests/gt1/gt1.pro new file mode 100644 index 00000000000..1ebd037c070 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt1/gt1.pro @@ -0,0 +1,10 @@ +include(../gtest_dependency.pri) + +TEMPLATE = app +CONFIG += console c++11 +CONFIG -= app_bundle +CONFIG += thread +CONFIG -= qt + +SOURCES += main.cpp \ + further.cpp diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt1/main.cpp b/src/plugins/autotest/unit_test/simple_gt/tests/gt1/main.cpp new file mode 100644 index 00000000000..a4a394b3105 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt1/main.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 +#include + +using namespace testing; + +int factorial(int a) +{ + if (a == 0) + return 1; + if (a == 1) + return a; + return a * factorial(a - 1); +} + +int factorial_it(int a) +{ + int result = 1; + for (int i = 2; i <= a; ++i) + result *= i; + return result; +} + +TEST(FactorialTest, HandlesZeroInput) +{ + EXPECT_EQ(1, factorial(0)); +} + +TEST(FactorialTest, HandlesPositiveInput) +{ + ASSERT_EQ(1, factorial(1)); + ASSERT_THAT(factorial(2), Eq(2)); + EXPECT_EQ(6, factorial(3)); + EXPECT_EQ(40320, factorial(8)); +} + +TEST(FactorialTest_Iterative, HandlesZeroInput) +{ + ASSERT_EQ(1, factorial_it(0)); +} + +TEST(FactorialTest_Iterative, DISABLED_HandlesPositiveInput) +{ + ASSERT_EQ(1, factorial_it(1)); + ASSERT_EQ(2, factorial_it(2)); + ASSERT_EQ(6, factorial_it(3)); + ASSERT_EQ(40320, factorial_it(8)); +} + +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt2/gt2.pro b/src/plugins/autotest/unit_test/simple_gt/tests/gt2/gt2.pro new file mode 100644 index 00000000000..65796da9abe --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt2/gt2.pro @@ -0,0 +1,12 @@ +include(../gtest_dependency.pri) + +TEMPLATE = app +CONFIG += qt console c++11 +CONFIG -= app_bundle + +HEADERS += \ + queuetest.h + +SOURCES += \ + main.cpp + diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt2/main.cpp b/src/plugins/autotest/unit_test/simple_gt/tests/gt2/main.cpp new file mode 100644 index 00000000000..32c52f646c6 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt2/main.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 +#include + +#include "queuetest.h" + +using namespace testing; + +TEST_F(QueueTest, IsEmptyInitialized) +{ + EXPECT_EQ(0, m_queue0.size()); +} + +TEST_F(QueueTest, DequeueWorks) +{ + ASSERT_FALSE(m_queue1.isEmpty()); + int n = m_queue1.dequeue(); + ASSERT_TRUE(m_queue1.isEmpty()); + EXPECT_EQ(1, n); + + ASSERT_FALSE(m_queue2.isEmpty()); + float f = m_queue2.dequeue(); + ASSERT_TRUE(m_queue2.isEmpty()); + EXPECT_EQ(1.0f, f); +} + +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt2/queuetest.h b/src/plugins/autotest/unit_test/simple_gt/tests/gt2/queuetest.h new file mode 100644 index 00000000000..2ba330d6f68 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt2/queuetest.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 + +class QueueTest : public ::testing::Test +{ +protected: + virtual void SetUp() { + m_queue1.enqueue(1); + m_queue2.enqueue(1.0f); + } + + QQueue m_queue0; + QQueue m_queue1; + QQueue m_queue2; +}; diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt3/dummytest.h b/src/plugins/autotest/unit_test/simple_gt/tests/gt3/dummytest.h new file mode 100644 index 00000000000..f92c1a1b160 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt3/dummytest.h @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 + +#include + +class DummyTest : public ::testing::TestWithParam +{ +protected: + virtual void SetUp() { + m_str1 = QString::fromLatin1(GetParam()).toLower(); + + m_str2 = QString::fromLatin1(GetParam()).toUpper(); + + m_str3 = QString::fromLatin1(GetParam()).toHtmlEscaped(); + } + + QString m_str1; + QString m_str2; + QString m_str3; +}; diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt3/gt3.pro b/src/plugins/autotest/unit_test/simple_gt/tests/gt3/gt3.pro new file mode 100644 index 00000000000..4830351f08f --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt3/gt3.pro @@ -0,0 +1,12 @@ +include(../gtest_dependency.pri) + +TEMPLATE = app +CONFIG += qt console c++11 +CONFIG -= app_bundle + +HEADERS += \ + dummytest.h + +SOURCES += \ + main.cpp + diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gt3/main.cpp b/src/plugins/autotest/unit_test/simple_gt/tests/gt3/main.cpp new file mode 100644 index 00000000000..4f048fa177f --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gt3/main.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 +#include + +#include "dummytest.h" + +using namespace testing; + +#include +#include + +static QMap testValues = { + { "DummyTest", + {QStringLiteral("dummytest"), QStringLiteral("DUMMYTEST"), QStringLiteral("DummyTest")} + }, + { "Hello World", + {QStringLiteral("hello world"), QStringLiteral("HELLO WORLD"), QStringLiteral("Hello World")} + }, + { "#include \n#include \"test.h\"", + {QStringLiteral("#include \n#include \"test.h\""), + QStringLiteral("#INCLUDE \n#INCLUDE \"TEST.H\""), + QStringLiteral("#include <QString>\n#include "test.h"") + } + } +}; + +static QMap testValuesSec = { + { "BlAh", + {QStringLiteral("blah"), QStringLiteral("BLAH"), QStringLiteral("BlAh")} + }, + { "", + {QStringLiteral(""), QStringLiteral(""), QStringLiteral("<html>")} + }, +}; + +INSTANTIATE_TEST_CASE_P(First, DummyTest, ::testing::ValuesIn(testValues.keys())); +INSTANTIATE_TEST_CASE_P(Second, DummyTest, ::testing::ValuesIn(testValuesSec.keys())); + +TEST_P(DummyTest, Easy) +{ + // total wrong usage, but this is for testing purpose + bool first = testValues.contains(GetParam()); + bool second = testValuesSec.contains(GetParam()); + QStringList expected; + if (first) + expected = testValues.value(GetParam()); + else if (second) + expected = testValuesSec.value(GetParam()); + else + FAIL(); + + ASSERT_EQ(3, expected.size()); + + EXPECT_EQ(m_str1, expected.at(0)); + EXPECT_EQ(m_str2, expected.at(1)); + EXPECT_EQ(m_str3, expected.at(2)); +} + +TEST(DummyTest, BlaBlubb) +{ + ASSERT_EQ(3, testValues.size()); +} + +int main(int argc, char *argv[]) +{ + InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/gtest_dependency.pri b/src/plugins/autotest/unit_test/simple_gt/tests/gtest_dependency.pri new file mode 100644 index 00000000000..bd22edc2f20 --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/gtest_dependency.pri @@ -0,0 +1,25 @@ +isEmpty(GOOGLETEST_DIR):GOOGLETEST_DIR=$$(GOOGLETEST_DIR) + +isEmpty(GOOGLETEST_DIR):linux-* { + GTEST_SRCDIR = /usr/include/gtest + GMOCK_SRCDIR = /usr/include/gmock +} else { + GTEST_SRCDIR = $$GOOGLETEST_DIR/googletest + GMOCK_SRCDIR = $$GOOGLETEST_DIR/googlemock +} + +requires(exists($$GTEST_SRCDIR):exists($$GMOCK_SRCDIR)) +!exists($$GOOGLETEST_DIR):message("No googletest src dir found - set GOOGLETEST_DIR to enable.") + +DEFINES += \ + GTEST_LANG_CXX11 + +INCLUDEPATH *= \ + $$GTEST_SRCDIR \ + $$GTEST_SRCDIR/include \ + $$GMOCK_SRCDIR \ + $$GMOCK_SRCDIR/include + +SOURCES += \ + $$GTEST_SRCDIR/src/gtest-all.cc \ + $$GMOCK_SRCDIR/src/gmock-all.cc diff --git a/src/plugins/autotest/unit_test/simple_gt/tests/tests.pro b/src/plugins/autotest/unit_test/simple_gt/tests/tests.pro new file mode 100644 index 00000000000..424362adbdf --- /dev/null +++ b/src/plugins/autotest/unit_test/simple_gt/tests/tests.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +SUBDIRS += gt1 \ + gt2 \ + gt3 diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index baa76e50d90..6209602e6f7 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -3,6 +3,7 @@ include(../../qtcreator.pri) TEMPLATE = subdirs SUBDIRS = \ + autotest \ clangstaticanalyzer \ coreplugin \ texteditor \ diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs index 6505ce0822e..df939723438 100644 --- a/src/plugins/plugins.qbs +++ b/src/plugins/plugins.qbs @@ -6,6 +6,7 @@ Project { references: [ "analyzerbase/analyzerbase.qbs", "android/android.qbs", + "autotest/autotest.qbs", "autotoolsprojectmanager/autotoolsprojectmanager.qbs", "baremetal/baremetal.qbs", "bazaar/bazaar.qbs",