AutoTest: Add minimum support for debugging tests

This adds another context menu entry for items on the test tree
to allow debugging of a single test.

Task-number: QTCREATORBUG-16070
Change-Id: I98f56b0f22c94ad71f0b91d690383043ed27f1c7
Reviewed-by: hjk <hjk@theqtcompany.com>
Reviewed-by: David Schulz <david.schulz@theqtcompany.com>
This commit is contained in:
Christian Stenger
2016-06-15 12:29:24 +02:00
parent 0e923c2a75
commit 5988fd0f5c
19 changed files with 227 additions and 25 deletions

View File

@@ -91,7 +91,8 @@ HEADERS += \
quick/quicktest_utils.h \
quick/quicktestvisitors.h \
quick/quicktestframework.h \
testframeworkmanager.h
testframeworkmanager.h \
testrunconfiguration.h
RESOURCES += \
autotest.qrc

View File

@@ -10,6 +10,7 @@ QtcPlugin {
Depends { name: "QmlJS" }
Depends { name: "QmlJSTools" }
Depends { name: "Utils" }
Depends { name: "Debugger" }
pluginTestDepends: [
"QbsProjectManager",
@@ -71,7 +72,8 @@ QtcPlugin {
"itestparser.h",
"itestframework.h",
"testframeworkmanager.cpp",
"testframeworkmanager.h"
"testframeworkmanager.h",
"testrunconfiguration.h"
]
Group {

View File

@@ -4,7 +4,8 @@ QTC_PLUGIN_DEPENDS += \
coreplugin \
projectexplorer \
cpptools \
qmljstools
qmljstools \
debugger
QTC_LIB_DEPENDS += \
cplusplus \

View File

@@ -162,7 +162,7 @@ void AutotestPlugin::onRunAllTriggered()
TestRunner *runner = TestRunner::instance();
TestTreeModel *model = TestTreeModel::instance();
runner->setSelectedTests(model->getAllTestCases());
runner->prepareToRunTests();
runner->prepareToRunTests(TestRunner::Run);
}
void AutotestPlugin::onRunSelectedTriggered()
@@ -170,7 +170,7 @@ void AutotestPlugin::onRunSelectedTriggered()
TestRunner *runner = TestRunner::instance();
TestTreeModel *model = TestTreeModel::instance();
runner->setSelectedTests(model->getSelectedTests());
runner->prepareToRunTests();
runner->prepareToRunTests(TestRunner::Run);
}
void AutotestPlugin::updateMenuItemsEnabledState()

View File

@@ -133,6 +133,11 @@ TestConfiguration *GTestTreeItem::testConfiguration() const
return config;
}
TestConfiguration *GTestTreeItem::debugConfiguration() const
{
return testConfiguration();
}
// used as key inside getAllTestCases()/getSelectedTestCases() for Google Tests
class ProFileWithDisplayName
{

View File

@@ -53,7 +53,9 @@ public:
QVariant data(int column, int role) const override;
bool canProvideTestConfiguration() const override { return type() != Root; }
bool canProvideDebugConfiguration() const override { return type() != Root; }
TestConfiguration *testConfiguration() const override;
TestConfiguration *debugConfiguration() const override;
QList<TestConfiguration *> getAllTestConfigurations() const override;
QList<TestConfiguration *> getSelectedTestConfigurations() const override;
TestTreeItem *find(const TestParseResult *result) override;

View File

@@ -38,7 +38,9 @@ TestOutputReader *QtTestConfiguration::outputReader(const QFutureInterface<TestR
QStringList QtTestConfiguration::argumentsForTestRunner(const TestSettings &settings) const
{
QStringList arguments({"-xml"});
QStringList arguments;
if (m_runMode == Run)
arguments.append("-xml");
const QString &metricsOption = TestSettings::metricsTypeToOption(settings.metrics);
if (!metricsOption.isEmpty())

View File

@@ -33,10 +33,20 @@ namespace Internal {
class QtTestConfiguration : public TestConfiguration
{
public:
explicit QtTestConfiguration() {}
enum RunMode
{
Run,
Debug
};
explicit QtTestConfiguration(RunMode mode = Run) : m_runMode(mode) {}
TestOutputReader *outputReader(const QFutureInterface<TestResultPtr> &fi,
QProcess *app) const override;
QStringList argumentsForTestRunner(const TestSettings &settings) const override;
void setRunMode(RunMode mode) { m_runMode = mode; }
private:
RunMode m_runMode;
};
} // namespace Internal

View File

@@ -84,6 +84,11 @@ bool QtTestTreeItem::canProvideTestConfiguration() const
}
}
bool QtTestTreeItem::canProvideDebugConfiguration() const
{
return canProvideTestConfiguration();
}
TestConfiguration *QtTestTreeItem::testConfiguration() const
{
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
@@ -128,6 +133,13 @@ TestConfiguration *QtTestTreeItem::testConfiguration() const
return config;
}
TestConfiguration *QtTestTreeItem::debugConfiguration() const
{
QtTestConfiguration *config = static_cast<QtTestConfiguration *>(testConfiguration());
config->setRunMode(QtTestConfiguration::Debug);
return config;
}
QList<TestConfiguration *> QtTestTreeItem::getAllTestConfigurations() const
{
QList<TestConfiguration *> result;

View File

@@ -40,7 +40,9 @@ public:
QVariant data(int column, int role) const override;
bool canProvideTestConfiguration() const override;
bool canProvideDebugConfiguration() const override;
TestConfiguration *testConfiguration() const override;
TestConfiguration *debugConfiguration() const override;
QList<TestConfiguration *> getAllTestConfigurations() const override;
QList<TestConfiguration *> getSelectedTestConfigurations() const override;
TestTreeItem *find(const TestParseResult *result) override;

View File

@@ -25,6 +25,8 @@
#include "testconfiguration.h"
#include "testoutputreader.h"
#include "testrunconfiguration.h"
#include "testrunner.h"
#include "testsettings.h"
#include <cpptools/cppmodelmanager.h>
@@ -84,7 +86,7 @@ static bool isLocal(RunConfiguration *runConfiguration)
return DeviceTypeKitInformation::deviceTypeId(kit) == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE;
}
void TestConfiguration::completeTestInformation()
void TestConfiguration::completeTestInformation(int runMode)
{
QTC_ASSERT(!m_proFile.isEmpty(), return);
@@ -99,6 +101,7 @@ void TestConfiguration::completeTestInformation()
QString buildDir;
Project *targetProject = 0;
Utils::Environment env;
Target *runConfigTarget = 0;
bool hasDesktopTarget = false;
bool guessedRunConfiguration = false;
setProject(0);
@@ -152,6 +155,7 @@ void TestConfiguration::completeTestInformation()
workDir = Utils::FileUtils::normalizePathName(stdRunnable.workingDirectory);
env = stdRunnable.environment;
hasDesktopTarget = true;
runConfigTarget = rc->target();
break;
}
}
@@ -182,6 +186,8 @@ void TestConfiguration::completeTestInformation()
setEnvironment(env);
setProject(project);
setGuessedConfiguration(guessedRunConfiguration);
if (!guessedRunConfiguration && runMode == TestRunner::Debug)
m_runConfig = new TestRunConfiguration(runConfigTarget);
}
}

View File

@@ -44,6 +44,7 @@ namespace Internal {
class TestOutputReader;
class TestResult;
class TestRunConfiguration;
struct TestSettings;
using TestResultPtr = QSharedPointer<TestResult>;
@@ -55,7 +56,7 @@ public:
explicit TestConfiguration();
virtual ~TestConfiguration();
void completeTestInformation();
void completeTestInformation(int runMode);
void setTestCases(const QStringList &testCases);
void setTestCaseCount(int count);
@@ -79,6 +80,7 @@ public:
QString displayName() const { return m_displayName; }
Utils::Environment environment() const { return m_environment; }
ProjectExplorer::Project *project() const { return m_project.data(); }
TestRunConfiguration *runConfiguration() const { return m_runConfig; }
bool guessedConfiguration() const { return m_guessedConfiguration; }
virtual TestOutputReader *outputReader(const QFutureInterface<TestResultPtr> &fi,
@@ -97,6 +99,7 @@ private:
Utils::Environment m_environment;
QPointer<ProjectExplorer::Project> m_project;
bool m_guessedConfiguration = false;
TestRunConfiguration *m_runConfig = 0;
};
} // namespace Internal

View File

@@ -120,6 +120,7 @@ void TestNavigationWidget::contextMenuEvent(QContextMenuEvent *event)
QMenu menu;
QAction *runThisTest = 0;
QAction *debugThisTest = 0;
const QModelIndexList list = m_view->selectionModel()->selectedIndexes();
if (list.size() == 1) {
const QModelIndex index = list.first();
@@ -131,7 +132,17 @@ void TestNavigationWidget::contextMenuEvent(QContextMenuEvent *event)
runThisTest = new QAction(tr("Run This Test"), &menu);
runThisTest->setEnabled(enabled);
connect(runThisTest, &QAction::triggered,
this, &TestNavigationWidget::onRunThisTestTriggered);
this, [this] () {
onRunThisTestTriggered(TestRunner::Run);
});
}
if (item->canProvideDebugConfiguration()) {
debugThisTest = new QAction(tr("Debug This Test"), &menu);
debugThisTest->setEnabled(enabled);
connect(debugThisTest, &QAction::triggered,
this, [this] () {
onRunThisTestTriggered(TestRunner::Debug);
});
}
}
}
@@ -152,10 +163,13 @@ void TestNavigationWidget::contextMenuEvent(QContextMenuEvent *event)
deselectAll->setEnabled(enabled && hasTests);
rescan->setEnabled(enabled);
if (runThisTest) {
if (runThisTest)
menu.addAction(runThisTest);
if (debugThisTest)
menu.addAction(debugThisTest);
if (runThisTest || debugThisTest)
menu.addSeparator();
}
menu.addAction(runAll);
menu.addAction(runSelected);
menu.addSeparator();
@@ -260,21 +274,32 @@ void TestNavigationWidget::initializeFilterMenu()
m_filterMenu->addAction(action);
}
void TestNavigationWidget::onRunThisTestTriggered()
void TestNavigationWidget::onRunThisTestTriggered(TestRunner::Mode runMode)
{
const QModelIndexList selected = m_view->selectionModel()->selectedIndexes();
// paranoia
if (selected.isEmpty())
return;
const QModelIndex sourceIndex = m_sortFilterModel->mapToSource(selected.first());
const QModelIndex &sourceIndex = m_sortFilterModel->mapToSource(selected.first());
if (!sourceIndex.isValid())
return;
TestTreeItem *item = static_cast<TestTreeItem *>(sourceIndex.internalPointer());
if (TestConfiguration *configuration = item->testConfiguration()) {
TestConfiguration *configuration;
switch (runMode) {
case TestRunner::Run:
configuration = item->testConfiguration();
break;
case TestRunner::Debug:
configuration = item->debugConfiguration();
break;
default:
configuration = 0;
}
if (configuration) {
TestRunner *runner = TestRunner::instance();
runner->setSelectedTests( {configuration} );
runner->prepareToRunTests();
runner->prepareToRunTests(runMode);
}
}

View File

@@ -25,6 +25,8 @@
#pragma once
#include "testrunner.h"
#include <coreplugin/inavigationwidgetfactory.h>
#include <utils/navigationtreeview.h>
@@ -74,7 +76,7 @@ private slots:
private:
void initializeFilterMenu();
void onRunThisTestTriggered();
void onRunThisTestTriggered(TestRunner::Mode runMode);
TestTreeModel *m_model;
TestTreeSortFilterModel *m_sortFilterModel;

View File

@@ -397,14 +397,14 @@ void TestResultsPane::onRunAllTriggered()
{
TestRunner *runner = TestRunner::instance();
runner->setSelectedTests(TestTreeModel::instance()->getAllTestCases());
runner->prepareToRunTests();
runner->prepareToRunTests(TestRunner::Run);
}
void TestResultsPane::onRunSelectedTriggered()
{
TestRunner *runner = TestRunner::instance();
runner->setSelectedTests(TestTreeModel::instance()->getSelectedTests());
runner->prepareToRunTests();
runner->prepareToRunTests(TestRunner::Run);
}
void TestResultsPane::initializeFilterMenu()

View File

@@ -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.
**
****************************************************************************/
#pragma once
#include <projectexplorer/runconfiguration.h>
namespace Autotest {
namespace Internal {
class TestRunConfiguration : public ProjectExplorer::RunConfiguration
{
public:
TestRunConfiguration(ProjectExplorer::Target *parent)
: ProjectExplorer::RunConfiguration(parent, "AutoTest.TestRunConfig")
{
setDefaultDisplayName(tr("AutoTest Debug"));
addExtraAspects();
}
private:
QWidget *createConfigurationWidget() { return 0; }
};
} // namespace Internal
} // namespace Autotest

View File

@@ -28,6 +28,7 @@
#include "autotestconstants.h"
#include "autotestplugin.h"
#include "testresultspane.h"
#include "testrunconfiguration.h"
#include "testsettings.h"
#include "testoutputreader.h"
@@ -38,6 +39,7 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorersettings.h>
#include <projectexplorer/target.h>
#include <utils/runextensions.h>
@@ -45,6 +47,9 @@
#include <QFutureInterface>
#include <QTime>
#include <debugger/debuggerruncontrol.h>
#include <debugger/debuggerstartparameters.h>
namespace Autotest {
namespace Internal {
@@ -125,7 +130,7 @@ static void performTestRun(QFutureInterface<TestResultPtr> &futureInterface,
QEventLoop eventLoop;
int testCaseCount = 0;
foreach (TestConfiguration *config, selectedTests) {
config->completeTestInformation();
config->completeTestInformation(TestRunner::Run);
if (config->project()) {
testCaseCount += config->testCaseCount();
} else {
@@ -208,8 +213,9 @@ static void performTestRun(QFutureInterface<TestResultPtr> &futureInterface,
futureInterface.setProgressValue(testCaseCount);
}
void TestRunner::prepareToRunTests()
void TestRunner::prepareToRunTests(Mode mode)
{
m_runMode = mode;
ProjectExplorer::Internal::ProjectExplorerSettings projectExplorerSettings =
ProjectExplorer::ProjectExplorerPlugin::projectExplorerSettings();
if (projectExplorerSettings.buildBeforeDeploy && !projectExplorerSettings.saveBeforeBuild) {
@@ -251,7 +257,7 @@ void TestRunner::prepareToRunTests()
}
if (!projectExplorerSettings.buildBeforeDeploy) {
runTests();
runOrDebugTests();
} else {
if (project->hasActiveBuildSettings()) {
buildProject(project);
@@ -272,6 +278,70 @@ void TestRunner::runTests()
Core::ProgressManager::addTask(future, tr("Running Tests"), Autotest::Constants::TASK_INDEX);
}
void TestRunner::debugTests()
{
// TODO improve to support more than one test configuration
QTC_ASSERT(m_selectedTests.size() == 1, onFinished();return);
TestConfiguration *config = m_selectedTests.first();
config->completeTestInformation(Debug);
if (!config->runConfiguration()) {
emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal,
TestRunner::tr("Failed to get run configuration."))));
onFinished();
return;
}
const QString &commandFilePath = executableFilePath(config->targetFile(),
config->environment().toProcessEnvironment());
if (commandFilePath.isEmpty()) {
emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal,
TestRunner::tr("Could not find command \"%1\". (%2)")
.arg(config->targetFile())
.arg(config->displayName()))));
onFinished();
return;
}
Debugger::DebuggerStartParameters sp;
sp.inferior.executable = commandFilePath;
sp.inferior.commandLineArguments = config->argumentsForTestRunner(
*AutotestPlugin::instance()->settings()).join(' ');
sp.inferior.environment = config->environment();
sp.inferior.workingDirectory = config->workingDirectory();
sp.displayName = config->displayName();
QString errorMessage;
Debugger::DebuggerRunControl *runControl = Debugger::createDebuggerRunControl(
sp, config->runConfiguration(), &errorMessage);
if (!runControl) {
emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal,
TestRunner::tr("Failed to create run configuration.\n%1").arg(errorMessage))));
onFinished();
return;
}
connect(runControl, &Debugger::DebuggerRunControl::finished, this, &TestRunner::onFinished);
ProjectExplorer::ProjectExplorerPlugin::startRunControl(
runControl, ProjectExplorer::Constants::DEBUG_RUN_MODE);
}
void TestRunner::runOrDebugTests()
{
switch (m_runMode) {
case Run:
runTests();
break;
case Debug:
debugTests();
break;
default:
QTC_ASSERT(false, return); // unexpected run mode
}
}
void TestRunner::buildProject(ProjectExplorer::Project *project)
{
ProjectExplorer::BuildManager *buildManager = ProjectExplorer::BuildManager::instance();
@@ -290,7 +360,7 @@ void TestRunner::buildFinished(bool success)
this, &TestRunner::buildFinished);
if (success) {
runTests();
runOrDebugTests();
} else {
emit testResultReady(TestResultPtr(new FaultyTestResult(Result::MessageFatal,
tr("Build failed. Canceling test run."))));

View File

@@ -44,6 +44,12 @@ class TestRunner : public QObject
Q_OBJECT
public:
enum Mode
{
Run,
Debug
};
static TestRunner* instance();
~TestRunner();
@@ -57,7 +63,7 @@ signals:
void testResultReady(const TestResultPtr &result);
public slots:
void prepareToRunTests();
void prepareToRunTests(Mode mode);
private slots:
void buildProject(ProjectExplorer::Project *project);
@@ -66,11 +72,14 @@ private slots:
private:
void runTests();
void debugTests();
void runOrDebugTests();
explicit TestRunner(QObject *parent = 0);
QFutureWatcher<TestResultPtr> m_futureWatcher;
QList<TestConfiguration *> m_selectedTests;
bool m_executingTests;
Mode m_runMode = Run;
// temporarily used if building before running is necessary
QMetaObject::Connection m_buildConnect;

View File

@@ -102,7 +102,9 @@ public:
TestTreeItem *findChildByNameAndFile(const QString &name, const QString &filePath);
virtual bool canProvideTestConfiguration() const { return false; }
virtual bool canProvideDebugConfiguration() const { return false; }
virtual TestConfiguration *testConfiguration() const { return 0; }
virtual TestConfiguration *debugConfiguration() const { return 0; }
virtual QList<TestConfiguration *> getAllTestConfigurations() const;
virtual QList<TestConfiguration *> getSelectedTestConfigurations() const;
virtual bool lessThan(const TestTreeItem *other, SortMode mode) const;