forked from qt-creator/qt-creator
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:
@@ -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
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
|
@@ -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()
|
||||
|
@@ -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")
|
||||
|
@@ -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; }
|
||||
|
@@ -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;
|
||||
|
@@ -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());
|
||||
|
||||
|
@@ -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(' '));
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user