AutoTest: Test execution from result output pane

Enable test execution via context menu in the result output pane.
At the moment only gtest is supported.

Task-number: QTCREATORBUG-16695
Change-Id: Ib39164c3cb44d249647b11e25dc51c9ac5db89c5
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Claus Steuer
2017-09-09 16:46:43 +02:00
parent 8b80442131
commit 4c04cff7c1
11 changed files with 153 additions and 102 deletions

View File

@@ -74,13 +74,13 @@ void GTestOutputReader::processOutput(const QByteArray &outputLine)
m_description = line;
if (m_iteration > 1)
m_description.append(' ' + tr("(iteration %1)").arg(m_iteration));
TestResultPtr testResult = TestResultPtr(new GTestResult);
TestResultPtr testResult = TestResultPtr(new GTestResult(m_projectFile));
testResult->setResult(Result::MessageInternal);
testResult->setDescription(m_description);
m_futureInterface.reportResult(testResult);
m_description.clear();
} else if (disabledTests.exactMatch(line)) {
TestResultPtr testResult = TestResultPtr(new GTestResult);
TestResultPtr testResult = TestResultPtr(new GTestResult(m_projectFile));
testResult->setResult(Result::MessageDisabledTests);
int disabled = disabledTests.cap(1).toInt();
testResult->setDescription(tr("You have %n disabled test(s).", 0, disabled));
@@ -98,7 +98,6 @@ void GTestOutputReader::processOutput(const QByteArray &outputLine)
m_futureInterface.reportResult(TestResultPtr(testResult));
m_currentTestName.clear();
m_currentTestSet.clear();
m_normalizedCurrentTestSet.clear();
} else if (newTestStarts.exactMatch(line)) {
setCurrentTestName(newTestStarts.cap(1));
TestResultPtr testResult = TestResultPtr(createDefaultResult());
@@ -112,7 +111,7 @@ void GTestOutputReader::processOutput(const QByteArray &outputLine)
m_futureInterface.reportResult(testResult);
} else if (newTestSetStarts.exactMatch(line)) {
setCurrentTestSet(newTestSetStarts.cap(1));
TestResultPtr testResult = TestResultPtr(new GTestResult);
TestResultPtr testResult = TestResultPtr(new GTestResult(m_projectFile));
testResult->setResult(Result::MessageCurrentTest);
testResult->setDescription(tr("Entering test set %1").arg(m_currentTestSet));
m_futureInterface.reportResult(testResult);
@@ -163,39 +162,21 @@ void GTestOutputReader::processOutput(const QByteArray &outputLine)
void GTestOutputReader::setCurrentTestSet(const QString &testSet)
{
m_currentTestSet = testSet;
m_normalizedCurrentTestSet = normalizeName(testSet);
}
void GTestOutputReader::setCurrentTestName(const QString &testName)
{
m_currentTestName = testName;
m_normalizedTestName = normalizeTestName(testName);
}
QString GTestOutputReader::normalizeName(const QString &name) const
{
static QRegExp parameterIndex("/\\d+");
QString nameWithoutParameterIndices = name;
nameWithoutParameterIndices.remove(parameterIndex);
return nameWithoutParameterIndices.split('/').last();
}
QString GTestOutputReader::normalizeTestName(const QString &testname) const
{
QString nameWithoutTypeParam = testname.split(',').first();
return normalizeName(nameWithoutTypeParam);
}
GTestResult *GTestOutputReader::createDefaultResult() const
{
GTestResult *result = new GTestResult(m_executable, m_currentTestName);
GTestResult *result = new GTestResult(m_executable, m_projectFile, m_currentTestName);
result->setTestSetName(m_currentTestSet);
result->setIteration(m_iteration);
const TestTreeItem *testItem = findTestTreeItemForCurrentLine();
const TestTreeItem *testItem = result->findTestTreeItem();
if (testItem && testItem->line()) {
result->setFileName(testItem->filePath());
result->setLine(static_cast<int>(testItem->line()));
@@ -204,43 +185,5 @@ GTestResult *GTestOutputReader::createDefaultResult() const
return result;
}
const TestTreeItem *GTestOutputReader::findTestTreeItemForCurrentLine() const
{
const auto item = TestTreeModel::instance()->findNonRootItem([&](const Utils::TreeItem *item) {
const TestTreeItem &treeItem = static_cast<const TestTreeItem &>(*item);
return matches(treeItem);
});
return static_cast<const TestTreeItem *>(item);
}
bool GTestOutputReader::matches(const TestTreeItem &treeItem) const
{
if (treeItem.proFile() != m_projectFile)
return false;
if (m_currentTestSet.isEmpty())
return matchesTestCase(treeItem);
return matchesTestFunctionOrSet(treeItem);
}
bool GTestOutputReader::matchesTestFunctionOrSet(const TestTreeItem &treeItem) const
{
if (treeItem.type() != TestTreeItem::TestFunctionOrSet)
return false;
const QString testItemTestSet = treeItem.parentItem()->name() + '.' + treeItem.name();
return testItemTestSet == m_normalizedCurrentTestSet;
}
bool GTestOutputReader::matchesTestCase(const TestTreeItem &treeItem) const
{
if (treeItem.type() != TestTreeItem::TestCase)
return false;
return treeItem.name() == m_normalizedTestName;
}
} // namespace Internal
} // namespace Autotest

View File

@@ -50,20 +50,12 @@ protected:
private:
void setCurrentTestSet(const QString &testSet);
void setCurrentTestName(const QString &testName);
QString normalizeName(const QString &name) const;
QString normalizeTestName(const QString &testname) const;
GTestResult *createDefaultResult() const;
const TestTreeItem *findTestTreeItemForCurrentLine() const;
bool matches(const TestTreeItem &treeItem) const;
bool matchesTestFunctionOrSet(const TestTreeItem &treeItem) const;
bool matchesTestCase(const TestTreeItem &treeItem) const;
QString m_executable;
QString m_projectFile;
QString m_currentTestName;
QString m_normalizedTestName;
QString m_currentTestSet;
QString m_normalizedCurrentTestSet;
QString m_description;
int m_iteration = 1;
};

View File

@@ -24,17 +24,20 @@
****************************************************************************/
#include "gtestresult.h"
#include "../testtreemodel.h"
#include "../testtreeitem.h"
namespace Autotest {
namespace Internal {
GTestResult::GTestResult(const QString &name)
: TestResult(name)
GTestResult::GTestResult(const QString &projectFile, const QString &name)
: TestResult(name), m_projectFile(projectFile)
{
}
GTestResult::GTestResult(const QString &executable, const QString &name)
: TestResult(executable, name)
GTestResult::GTestResult(const QString &executable, const QString &projectFile,
const QString &name)
: TestResult(executable, name), m_projectFile(projectFile)
{
}
@@ -68,5 +71,60 @@ bool GTestResult::isDirectParentOf(const TestResult *other, bool *needsIntermedi
return isTest() && gtOther->isTestSet();
}
static QString normalizeName(const QString &name)
{
static QRegExp parameterIndex("/\\d+");
QString nameWithoutParameterIndices = name;
nameWithoutParameterIndices.remove(parameterIndex);
return nameWithoutParameterIndices.split('/').last();
}
static QString normalizeTestName(const QString &testname)
{
QString nameWithoutTypeParam = testname.split(',').first();
return normalizeName(nameWithoutTypeParam);
}
const TestTreeItem *GTestResult::findTestTreeItem() const
{
const auto item = TestTreeModel::instance()->findNonRootItem([this](const Utils::TreeItem *item) {
const TestTreeItem &treeItem = static_cast<const TestTreeItem &>(*item);
return matches(treeItem);
});
return static_cast<const TestTreeItem *>(item);
}
bool GTestResult::matches(const TestTreeItem &treeItem) const
{
if (treeItem.proFile() != m_projectFile)
return false;
if (isTest())
return matchesTestCase(treeItem);
return matchesTestFunctionOrSet(treeItem);
}
bool GTestResult::matchesTestFunctionOrSet(const TestTreeItem &treeItem) const
{
if (treeItem.type() != TestTreeItem::TestFunctionOrSet)
return false;
const QString testItemTestSet = treeItem.parentItem()->name() + '.' + treeItem.name();
return testItemTestSet == normalizeName(m_testSetName);
}
bool GTestResult::matchesTestCase(const TestTreeItem &treeItem) const
{
if (treeItem.type() != TestTreeItem::TestCase)
return false;
return treeItem.name() == normalizeTestName(name());
}
} // namespace Internal
} // namespace Autotest

View File

@@ -33,17 +33,25 @@ namespace Internal {
class GTestResult : public TestResult
{
public:
explicit GTestResult(const QString &name = QString());
GTestResult(const QString &executable, const QString &name);
GTestResult(const QString &projectFile, const QString &name = QString());
GTestResult(const QString &executable, const QString &projectFile, const QString &name);
const QString outputString(bool selected) const override;
void setTestSetName(const QString &testSetName) { m_testSetName = testSetName; }
void setIteration(int iteration) { m_iteration = iteration; }
bool isDirectParentOf(const TestResult *other, bool *needsIntermediate) const override;
virtual const TestTreeItem *findTestTreeItem() const override;
private:
bool isTest() const { return m_testSetName.isEmpty(); }
bool isTestSet() const { return !m_testSetName.isEmpty(); }
bool matches(const TestTreeItem &item) const;
bool matchesTestFunctionOrSet(const TestTreeItem &treeItem) const;
bool matchesTestCase(const TestTreeItem &treeItem) const;
QString m_testSetName;
QString m_projectFile;
int m_iteration = 1;
};

View File

@@ -301,25 +301,7 @@ void TestNavigationWidget::onRunThisTestTriggered(TestRunMode runMode)
return;
TestTreeItem *item = static_cast<TestTreeItem *>(sourceIndex.internalPointer());
TestConfiguration *configuration;
switch (runMode) {
case TestRunMode::Run:
case TestRunMode::RunWithoutDeploy:
configuration = item->testConfiguration();
break;
case TestRunMode::Debug:
case TestRunMode::DebugWithoutDeploy:
configuration = item->debugConfiguration();
break;
default:
configuration = nullptr;
}
if (configuration) {
TestRunner *runner = TestRunner::instance();
runner->setSelectedTests({configuration});
runner->prepareToRunTests(runMode);
}
TestRunner::instance()->runTest(runMode, item);
}
TestNavigationWidgetFactory::TestNavigationWidgetFactory()

View File

@@ -58,6 +58,11 @@ const QString TestResult::outputString(bool selected) const
return selected ? m_description : m_description.split('\n').first();
}
const TestTreeItem *TestResult::findTestTreeItem() const
{
return nullptr;
}
Result::Type TestResult::resultFromString(const QString &resultString)
{
if (resultString == "pass")

View File

@@ -35,6 +35,8 @@
namespace Autotest {
namespace Internal {
class TestTreeItem;
namespace Result{
enum Type {
Pass, FIRST_TYPE = Pass,
@@ -76,6 +78,7 @@ public:
virtual ~TestResult() {}
virtual const QString outputString(bool selected) const;
virtual const TestTreeItem *findTestTreeItem() const;
QString executable() const { return m_executable; }
QString name() const { return m_name; }

View File

@@ -144,7 +144,7 @@ TestResultsPane::TestResultsPane(QObject *parent) :
connect(m_treeView, &Utils::TreeView::customContextMenuRequested,
this, &TestResultsPane::onCustomContextMenuRequested);
connect(m_treeView, &ResultsTreeView::copyShortcutTriggered, [this] () {
onCopyItemTriggered(m_treeView->currentIndex());
onCopyItemTriggered(getTestResult(m_treeView->currentIndex()));
});
connect(m_model, &TestResultModel::requestExpansion, [this] (QModelIndex idx) {
m_treeView->expand(m_filterModel->mapFromSource(idx));
@@ -562,11 +562,12 @@ 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);
const TestResult *clicked = getTestResult(m_treeView->indexAt(pos));
QMenu menu;
QAction *action = new QAction(tr("Copy"), &menu);
action->setShortcut(QKeySequence(QKeySequence::Copy));
action->setEnabled(resultsAvailable);
action->setEnabled(resultsAvailable && clicked);
connect(action, &QAction::triggered, [this, clicked] () {
onCopyItemTriggered(clicked);
});
@@ -582,14 +583,36 @@ void TestResultsPane::onCustomContextMenuRequested(const QPoint &pos)
connect(action, &QAction::triggered, this, &TestResultsPane::onSaveWholeTriggered);
menu.addAction(action);
action = new QAction(tr("Run This Test"), &menu);
action->setEnabled(clicked && clicked->findTestTreeItem());
connect(action, &QAction::triggered, this, [this, clicked] {
onRunThisTestTriggered(TestRunMode::Run, clicked);
});
menu.addAction(action);
action = new QAction(tr("Debug This Test"), &menu);
action->setEnabled(clicked && clicked->findTestTreeItem());
connect(action, &QAction::triggered, this, [this, clicked] {
onRunThisTestTriggered(TestRunMode::Debug, clicked);
});
menu.addAction(action);
menu.exec(m_treeView->mapToGlobal(pos));
}
void TestResultsPane::onCopyItemTriggered(const QModelIndex &idx)
const TestResult *TestResultsPane::getTestResult(const QModelIndex &idx)
{
if (!idx.isValid())
return;
return nullptr;
const TestResult *result = m_filterModel->testResult(idx);
QTC_CHECK(result);
return result;
}
void TestResultsPane::onCopyItemTriggered(const TestResult *result)
{
QTC_ASSERT(result, return);
QApplication::clipboard()->setText(result->outputString(true));
}
@@ -614,6 +637,16 @@ void TestResultsPane::onSaveWholeTriggered()
}
}
void TestResultsPane::onRunThisTestTriggered(TestRunMode runMode, const TestResult *result)
{
QTC_ASSERT(result, return);
const TestTreeItem *item = result->findTestTreeItem();
if (item)
TestRunner::instance()->runTest(runMode, item);
}
void TestResultsPane::toggleOutputStyle()
{
const bool displayText = m_outputWidget->currentIndex() == 0;

View File

@@ -52,6 +52,7 @@ namespace Internal {
class TestResultModel;
class TestResultFilterModel;
class TestResult;
class ResultsTreeView : public Utils::TreeView
{
@@ -109,9 +110,11 @@ private:
void onScrollBarRangeChanged(int, int max);
void updateRunActions();
void onCustomContextMenuRequested(const QPoint &pos);
void onCopyItemTriggered(const QModelIndex &idx);
const TestResult *getTestResult(const QModelIndex &idx);
void onCopyItemTriggered(const TestResult *result);
void onCopyWholeTriggered();
void onSaveWholeTriggered();
void onRunThisTestTriggered(TestRunMode runMode, const TestResult *result);
void toggleOutputStyle();
QString getWholeOutput(const QModelIndex &parent = QModelIndex());

View File

@@ -31,6 +31,7 @@
#include "testrunconfiguration.h"
#include "testsettings.h"
#include "testoutputreader.h"
#include "testtreeitem.h"
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/futureprogress.h>
@@ -106,6 +107,28 @@ void TestRunner::setSelectedTests(const QList<TestConfiguration *> &selected)
m_selectedTests = selected;
}
void TestRunner::runTest(TestRunMode mode, const TestTreeItem *item)
{
TestConfiguration *configuration;
switch (mode) {
case TestRunMode::Run:
case TestRunMode::RunWithoutDeploy:
configuration = item->testConfiguration();
break;
case TestRunMode::Debug:
case TestRunMode::DebugWithoutDeploy:
configuration = item->debugConfiguration();
break;
default:
configuration = nullptr;
}
if (configuration) {
setSelectedTests({configuration});
prepareToRunTests(mode);
}
}
static QString processInformation(const QProcess &proc)
{
QString information("\nCommand line: " + proc.program() + ' ' + proc.arguments().join(' '));

View File

@@ -55,6 +55,7 @@ public:
~TestRunner();
void setSelectedTests(const QList<TestConfiguration *> &selected);
void runTest(TestRunMode mode, const TestTreeItem *item);
bool isTestRunning() const { return m_executingTests; }
void prepareToRunTests(TestRunMode mode);