AutoTest: Make results tree a real tree

Provide a way to control how the results tree will be
generated for the respective test framework and use
this information to construct a real tree.
Basically this changes the layout of Qt test results,
but keeps the former layout of Google test results.

Task-number: QTCREATORBUG-17104
Change-Id: I7fca4d8e365bfebcca4cf7855cf6a882e5379143
Reviewed-by: David Schulz <david.schulz@qt.io>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Stenger
2016-10-31 13:11:52 +01:00
parent 5b6e9671d7
commit cdd94cbb02
12 changed files with 269 additions and 99 deletions

View File

@@ -52,5 +52,14 @@ const QString GTestResult::outputString(bool selected) const
return output; return output;
} }
bool GTestResult::isDirectParentOf(const TestResult *other, bool *needsIntermediate) const
{
if (!TestResult::isDirectParentOf(other, needsIntermediate))
return false;
const GTestResult *gtOther = static_cast<const GTestResult *>(other);
return isTest() && gtOther->isTestSet();
}
} // namespace Internal } // namespace Internal
} // namespace Autotest } // namespace Autotest

View File

@@ -37,8 +37,10 @@ public:
const QString outputString(bool selected) const override; const QString outputString(bool selected) const override;
void setTestSetName(const QString &testSetName) { m_testSetName = testSetName; } void setTestSetName(const QString &testSetName) { m_testSetName = testSetName; }
bool isDirectParentOf(const TestResult *other, bool *needsIntermediate) const override;
private: private:
bool isTest() const { return m_testSetName.isEmpty(); }
bool isTestSet() const { return !m_testSetName.isEmpty(); }
QString m_testSetName; QString m_testSetName;
}; };

View File

@@ -162,14 +162,18 @@ void QtTestOutputReader::processOutput(const QByteArray &outputLine)
if (currentTag == QStringLiteral("TestCase")) { if (currentTag == QStringLiteral("TestCase")) {
m_className = m_xmlReader.attributes().value(QStringLiteral("name")).toString(); m_className = m_xmlReader.attributes().value(QStringLiteral("name")).toString();
QTC_ASSERT(!m_className.isEmpty(), continue); QTC_ASSERT(!m_className.isEmpty(), continue);
TestResultPtr testResult = TestResultPtr(new QtTestResult(m_className)); TestResultPtr testResult = TestResultPtr(createDefaultResult());
testResult->setResult(Result::MessageTestCaseStart); testResult->setResult(Result::MessageTestCaseStart);
testResult->setDescription(tr("Executing test case %1").arg(m_className)); testResult->setDescription(tr("Executing test case %1").arg(m_className));
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
} else if (currentTag == QStringLiteral("TestFunction")) { } else if (currentTag == QStringLiteral("TestFunction")) {
m_testCase = m_xmlReader.attributes().value(QStringLiteral("name")).toString(); m_testCase = m_xmlReader.attributes().value(QStringLiteral("name")).toString();
QTC_ASSERT(!m_testCase.isEmpty(), continue); QTC_ASSERT(!m_testCase.isEmpty(), continue);
TestResultPtr testResult = TestResultPtr(new QtTestResult()); TestResultPtr testResult = TestResultPtr(createDefaultResult());
testResult->setResult(Result::MessageTestCaseStart);
testResult->setDescription(tr("Executing test function %1").arg(m_testCase));
m_futureInterface.reportResult(testResult);
testResult = TestResultPtr(new QtTestResult);
testResult->setResult(Result::MessageCurrentTest); testResult->setResult(Result::MessageCurrentTest);
testResult->setDescription(tr("Entering test function %1::%2").arg(m_className, testResult->setDescription(tr("Entering test function %1::%2").arg(m_className,
m_testCase)); m_testCase));
@@ -179,7 +183,6 @@ void QtTestOutputReader::processOutput(const QByteArray &outputLine)
QTC_ASSERT(!m_duration.isEmpty(), continue); QTC_ASSERT(!m_duration.isEmpty(), continue);
} else if (currentTag == QStringLiteral("Message") } else if (currentTag == QStringLiteral("Message")
|| currentTag == QStringLiteral("Incident")) { || currentTag == QStringLiteral("Incident")) {
m_dataTag.clear();
m_description.clear(); m_description.clear();
m_duration.clear(); m_duration.clear();
m_file.clear(); m_file.clear();
@@ -189,15 +192,15 @@ void QtTestOutputReader::processOutput(const QByteArray &outputLine)
m_result = TestResult::resultFromString( m_result = TestResult::resultFromString(
attributes.value(QStringLiteral("type")).toString()); attributes.value(QStringLiteral("type")).toString());
m_file = decode(attributes.value(QStringLiteral("file")).toString()); m_file = decode(attributes.value(QStringLiteral("file")).toString());
if (!m_file.isEmpty()) { if (!m_file.isEmpty())
m_file = constructSourceFilePath(m_buildDir, m_file); m_file = constructSourceFilePath(m_buildDir, m_file);
}
m_lineNumber = attributes.value(QStringLiteral("line")).toInt(); m_lineNumber = attributes.value(QStringLiteral("line")).toInt();
} else if (currentTag == QStringLiteral("BenchmarkResult")) { } else if (currentTag == QStringLiteral("BenchmarkResult")) {
const QXmlStreamAttributes &attributes = m_xmlReader.attributes(); const QXmlStreamAttributes &attributes = m_xmlReader.attributes();
const QString metric = attributes.value(QStringLiteral("metric")).toString(); const QString metric = attributes.value(QStringLiteral("metric")).toString();
const double value = attributes.value(QStringLiteral("value")).toDouble(); const double value = attributes.value(QStringLiteral("value")).toDouble();
const int iterations = attributes.value(QStringLiteral("iterations")).toInt(); const int iterations = attributes.value(QStringLiteral("iterations")).toInt();
m_dataTag = attributes.value(QStringLiteral("tag")).toString();
m_description = constructBenchmarkInformation(metric, value, iterations); m_description = constructBenchmarkInformation(metric, value, iterations);
m_result = Result::Benchmark; m_result = Result::Benchmark;
} else if (currentTag == QStringLiteral("DataTag")) { } else if (currentTag == QStringLiteral("DataTag")) {
@@ -250,30 +253,31 @@ void QtTestOutputReader::processOutput(const QByteArray &outputLine)
m_cdataMode = None; m_cdataMode = None;
const QStringRef currentTag = m_xmlReader.name(); const QStringRef currentTag = m_xmlReader.name();
if (currentTag == QStringLiteral("TestFunction")) { if (currentTag == QStringLiteral("TestFunction")) {
if (!m_duration.isEmpty()) { QtTestResult *testResult = createDefaultResult();
QtTestResult *testResult = new QtTestResult(m_className); testResult->setResult(Result::MessageTestCaseEnd);
testResult->setFunctionName(m_testCase); testResult->setDescription(
testResult->setResult(Result::MessageInternal); m_duration.isEmpty() ? tr("Test function finished.")
testResult->setDescription(tr("Execution took %1 ms.").arg(m_duration)); : tr("Execution took %1 ms.").arg(m_duration));
m_futureInterface.reportResult(TestResultPtr(testResult)); m_futureInterface.reportResult(TestResultPtr(testResult));
}
m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1);
m_dataTag.clear();
m_testCase.clear();
} else if (currentTag == QStringLiteral("TestCase")) { } else if (currentTag == QStringLiteral("TestCase")) {
QtTestResult *testResult = new QtTestResult(m_className); QtTestResult *testResult = createDefaultResult();
testResult->setResult(Result::MessageTestCaseEnd); testResult->setResult(Result::MessageTestCaseEnd);
testResult->setDescription( testResult->setDescription(
m_duration.isEmpty() ? tr("Test finished.") m_duration.isEmpty() ? tr("Test finished.")
: tr("Test execution took %1 ms.").arg(m_duration)); : tr("Test execution took %1 ms.").arg(m_duration));
m_futureInterface.reportResult(TestResultPtr(testResult)); m_futureInterface.reportResult(TestResultPtr(testResult));
} else if (validEndTags.contains(currentTag.toString())) { } else if (validEndTags.contains(currentTag.toString())) {
QtTestResult *testResult = new QtTestResult(m_className); QtTestResult *testResult = createDefaultResult();
testResult->setFunctionName(m_testCase);
testResult->setDataTag(m_dataTag);
testResult->setResult(m_result); testResult->setResult(m_result);
testResult->setFileName(m_file); testResult->setFileName(m_file);
testResult->setLine(m_lineNumber); testResult->setLine(m_lineNumber);
testResult->setDescription(m_description); testResult->setDescription(m_description);
m_futureInterface.reportResult(TestResultPtr(testResult)); m_futureInterface.reportResult(TestResultPtr(testResult));
if (currentTag == QStringLiteral("Incident"))
m_dataTag.clear();
} }
break; break;
} }
@@ -283,5 +287,13 @@ void QtTestOutputReader::processOutput(const QByteArray &outputLine)
} }
} }
QtTestResult *QtTestOutputReader::createDefaultResult() const
{
QtTestResult *result = new QtTestResult(m_className);
result->setFunctionName(m_testCase);
result->setDataTag(m_dataTag);
return result;
}
} // namespace Internal } // namespace Internal
} // namespace Autotest } // namespace Autotest

View File

@@ -33,6 +33,8 @@
namespace Autotest { namespace Autotest {
namespace Internal { namespace Internal {
class QtTestResult;
class QtTestOutputReader : public TestOutputReader class QtTestOutputReader : public TestOutputReader
{ {
Q_DECLARE_TR_FUNCTIONS(Autotest::Internal::QtTestOutputReader) Q_DECLARE_TR_FUNCTIONS(Autotest::Internal::QtTestOutputReader)
@@ -45,6 +47,8 @@ protected:
void processOutput(const QByteArray &outputLine) override; void processOutput(const QByteArray &outputLine) override;
private: private:
QtTestResult *createDefaultResult() const;
enum CDATAMode enum CDATAMode
{ {
None, None,

View File

@@ -25,6 +25,8 @@
#include "qttestresult.h" #include "qttestresult.h"
#include <utils/qtcassert.h>
namespace Autotest { namespace Autotest {
namespace Internal { namespace Internal {
@@ -71,5 +73,47 @@ const QString QtTestResult::outputString(bool selected) const
return output; return output;
} }
bool QtTestResult::isDirectParentOf(const TestResult *other, bool *needsIntermediate) const
{
if (!TestResult::isDirectParentOf(other, needsIntermediate))
return false;
const QtTestResult *qtOther = static_cast<const QtTestResult *>(other);
if (result() == Result::MessageTestCaseStart || result() == Result::MessageIntermediate) {
if (qtOther->isDataTag()) {
if (qtOther->m_function == m_function) {
if (m_dataTag.isEmpty()) {
// avoid adding function's TestCaseEnd to the data tag
*needsIntermediate = qtOther->result() != Result::MessageTestCaseEnd;
return true;
}
return qtOther->m_dataTag == m_dataTag;
}
} else if (qtOther->isTestFunction()) {
return isTestCase() || m_function == qtOther->m_function;
}
}
return false;
}
bool QtTestResult::isIntermediateFor(const TestResult *other) const
{
QTC_ASSERT(other, return false);
const QtTestResult *qtOther = static_cast<const QtTestResult *>(other);
return m_dataTag == qtOther->m_dataTag && m_function == qtOther->m_function
&& name() == qtOther->name();
}
TestResult *QtTestResult::createIntermediateResultFor(const TestResult *other)
{
QTC_ASSERT(other, return 0);
const QtTestResult *qtOther = static_cast<const QtTestResult *>(other);
QtTestResult *intermediate = new QtTestResult(qtOther->name());
intermediate->m_function = qtOther->m_function;
intermediate->m_dataTag = qtOther->m_dataTag;
// intermediates will be needed only for data tags
intermediate->setDescription("Data tag: " + qtOther->m_dataTag);
return intermediate;
}
} // namespace Internal } // namespace Internal
} // namespace Autotest } // namespace Autotest

View File

@@ -39,7 +39,14 @@ public:
void setFunctionName(const QString &functionName) { m_function = functionName; } void setFunctionName(const QString &functionName) { m_function = functionName; }
void setDataTag(const QString &dataTag) { m_dataTag = dataTag; } void setDataTag(const QString &dataTag) { m_dataTag = dataTag; }
bool isDirectParentOf(const TestResult *other, bool *needsIntermediate) const override;
bool isIntermediateFor(const TestResult *other) const override;
TestResult *createIntermediateResultFor(const TestResult *other) override;
private: private:
bool isTestCase() const { return m_function.isEmpty() && m_dataTag.isEmpty(); }
bool isTestFunction() const { return !m_function.isEmpty() && m_dataTag.isEmpty(); }
bool isDataTag() const { return !m_function.isEmpty() && !m_dataTag.isEmpty(); }
QString m_function; QString m_function;
QString m_dataTag; QString m_dataTag;
}; };

View File

@@ -25,6 +25,7 @@
#include "testresult.h" #include "testresult.h"
#include <utils/qtcassert.h>
#include <utils/theme/theme.h> #include <utils/theme/theme.h>
namespace Autotest { namespace Autotest {
@@ -158,5 +159,24 @@ QColor TestResult::colorForType(const Result::Type type)
} }
} }
bool TestResult::isDirectParentOf(const TestResult *other, bool * /*needsIntermediate*/) const
{
QTC_ASSERT(other, return false);
return m_name == other->m_name;
}
bool TestResult::isIntermediateFor(const TestResult *other) const
{
QTC_ASSERT(other, return false);
return m_name == other->m_name;
}
TestResult *TestResult::createIntermediateResultFor(const TestResult *other)
{
QTC_ASSERT(other, return 0);
TestResult *intermediate = new TestResult(other->m_name);
return intermediate;
}
} // namespace Internal } // namespace Internal
} // namespace Autotest } // namespace Autotest

View File

@@ -59,6 +59,7 @@ enum Type {
MessageTestCaseFail, MessageTestCaseFail,
MessageTestCaseEnd, MessageTestCaseEnd,
MessageTestCaseRepetition, MessageTestCaseRepetition,
MessageIntermediate,
MessageCurrentTest, INTERNAL_MESSAGES_END = MessageCurrentTest, MessageCurrentTest, INTERNAL_MESSAGES_END = MessageCurrentTest,
Invalid, Invalid,
@@ -91,6 +92,10 @@ public:
static QString resultToString(const Result::Type type); static QString resultToString(const Result::Type type);
static QColor colorForType(const Result::Type type); static QColor colorForType(const Result::Type type);
virtual bool isDirectParentOf(const TestResult *other, bool *needsIntermediate) const;
virtual bool isIntermediateFor(const TestResult *other) const;
virtual TestResult *createIntermediateResultFor(const TestResult *other);
private: private:
QString m_name; QString m_name;
Result::Type m_result = Result::Invalid; Result::Type m_result = Result::Invalid;

View File

@@ -31,7 +31,6 @@
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <QAbstractItemView> #include <QAbstractItemView>
#include <QDebug>
#include <QPainter> #include <QPainter>
#include <QTextLayout> #include <QTextLayout>
@@ -49,15 +48,13 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
{ {
QStyleOptionViewItem opt = option; QStyleOptionViewItem opt = option;
initStyleOption(&opt, index); 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(); painter->save();
QFontMetrics fm(opt.font); QFontMetrics fm(opt.font);
QColor foreground; QColor foreground;
const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt.widget); const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt.widget);
const bool selected = view->selectionModel()->currentIndex() == index; const bool selected = opt.state & QStyle::State_Selected;
if (selected) { if (selected) {
painter->setBrush(opt.palette.highlight().color()); painter->setBrush(opt.palette.highlight().color());
@@ -75,11 +72,6 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
const TestResult *testResult = resultFilterModel->testResult(index); const TestResult *testResult = resultFilterModel->testResult(index);
QTC_ASSERT(testResult, painter->restore();return); QTC_ASSERT(testResult, painter->restore();return);
// draw the indicator by ourself as we paint across it with the delegate
QStyleOptionViewItem 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<QIcon>(); QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
if (!icon.isNull()) if (!icon.isNull())
painter->drawPixmap(positions.left(), positions.top(), painter->drawPixmap(positions.left(), positions.top(),
@@ -140,7 +132,6 @@ QSize TestResultDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo
QStyleOptionViewItem opt = option; QStyleOptionViewItem opt = option;
// make sure opt.rect is initialized correctly - otherwise we might get a width of 0 // make sure opt.rect is initialized correctly - otherwise we might get a width of 0
opt.initFrom(opt.widget); opt.initFrom(opt.widget);
initStyleOption(&opt, index);
const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt.widget); const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt.widget);
const bool selected = view->selectionModel()->currentIndex() == index; const bool selected = view->selectionModel()->currentIndex() == index;

View File

@@ -67,7 +67,14 @@ private:
m_typeAreaWidth = QFontMetrics(options.font).width("XXXXXXXX"); m_typeAreaWidth = QFontMetrics(options.font).width("XXXXXXXX");
m_indentation = options.widget ? options.widget->style()->pixelMetric( m_indentation = options.widget ? options.widget->style()->pixelMetric(
QStyle::PM_TreeViewIndentation, &options) : 0; QStyle::PM_TreeViewIndentation, &options) : 0;
m_level = filterModel->mapToSource(options.index).parent() == srcModel->rootItem()->index() ? 1 : 2;
const QModelIndex &rootIndex = srcModel->rootItem()->index();
QModelIndex parentIndex = filterModel->mapToSource(options.index).parent();
m_level = 1;
while (rootIndex != parentIndex) {
++m_level;
parentIndex = parentIndex.parent();
}
int flexibleArea = lineAreaLeft() - textAreaLeft() - ITEM_SPACING; int flexibleArea = lineAreaLeft() - textAreaLeft() - ITEM_SPACING;
if (m_maxFileLength > flexibleArea / 2) if (m_maxFileLength > flexibleArea / 2)
m_realFileLength = flexibleArea / 2; m_realFileLength = flexibleArea / 2;
@@ -84,12 +91,10 @@ private:
int fontHeight() const { return m_fontHeight; } int fontHeight() const { return m_fontHeight; }
int typeAreaLeft() const { return left() + ICON_SIZE + ITEM_SPACING; } int typeAreaLeft() const { return left() + ICON_SIZE + ITEM_SPACING; }
int typeAreaWidth() const { return m_typeAreaWidth; } int typeAreaWidth() const { return m_typeAreaWidth; }
int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ITEM_SPACING int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ITEM_SPACING; }
+ (1 - m_level) * m_indentation; }
int textAreaWidth() const { return fileAreaLeft() - ITEM_SPACING - textAreaLeft(); } int textAreaWidth() const { return fileAreaLeft() - ITEM_SPACING - textAreaLeft(); }
int fileAreaLeft() const { return lineAreaLeft() - ITEM_SPACING - m_realFileLength; } int fileAreaLeft() const { return lineAreaLeft() - ITEM_SPACING - m_realFileLength; }
int lineAreaLeft() const { return right() - m_maxLineLength; } int lineAreaLeft() const { return right() - m_maxLineLength; }
int indentation() const { return m_indentation; }
QRect typeArea() const { return QRect(typeAreaLeft(), top(), QRect typeArea() const { return QRect(typeAreaLeft(), top(),
typeAreaWidth(), m_fontHeight); } typeAreaWidth(), m_fontHeight); }

View File

@@ -99,7 +99,8 @@ void TestResultItem::updateDescription(const QString &description)
void TestResultItem::updateResult() void TestResultItem::updateResult()
{ {
if (m_testResult->result() != Result::MessageTestCaseStart) if (m_testResult->result() != Result::MessageTestCaseStart
&& m_testResult->result() != Result::MessageIntermediate)
return; return;
Result::Type newResult = Result::MessageTestCaseSuccess; Result::Type newResult = Result::MessageTestCaseSuccess;
@@ -110,6 +111,7 @@ void TestResultItem::updateResult()
case Result::Fail: case Result::Fail:
case Result::MessageFatal: case Result::MessageFatal:
case Result::UnexpectedPass: case Result::UnexpectedPass:
case Result::MessageTestCaseFail:
m_testResult->setResult(Result::MessageTestCaseFail); m_testResult->setResult(Result::MessageTestCaseFail);
return; return;
case Result::ExpectedFail: case Result::ExpectedFail:
@@ -117,6 +119,7 @@ void TestResultItem::updateResult()
case Result::Skip: case Result::Skip:
case Result::BlacklistedFail: case Result::BlacklistedFail:
case Result::BlacklistedPass: case Result::BlacklistedPass:
case Result::MessageTestCaseWarn:
newResult = Result::MessageTestCaseWarn; newResult = Result::MessageTestCaseWarn;
break; break;
default: {} default: {}
@@ -126,6 +129,40 @@ void TestResultItem::updateResult()
m_testResult->setResult(newResult); m_testResult->setResult(newResult);
} }
void TestResultItem::updateIntermediateChildren()
{
for (Utils::TreeItem *child : children()) {
TestResultItem *childItem = static_cast<TestResultItem *>(child);
if (childItem->testResult()->result() == Result::MessageIntermediate)
childItem->updateResult();
}
}
TestResultItem *TestResultItem::intermediateFor(const TestResultItem *item) const
{
QTC_ASSERT(item, return nullptr);
const TestResult *otherResult = item->testResult();
for (int row = childCount() - 1; row >= 0; --row) {
TestResultItem *child = static_cast<TestResultItem *>(childAt(row));
const TestResult *testResult = child->testResult();
if (testResult->result() != Result::MessageIntermediate)
continue;
if (testResult->isIntermediateFor(otherResult))
return child;
}
return 0;
}
TestResultItem *TestResultItem::createAndAddIntermediateFor(const TestResultItem *child)
{
TestResultPtr result(m_testResult->createIntermediateResultFor(child->testResult()));
QTC_ASSERT(!result.isNull(), return 0);
result->setResult(Result::MessageIntermediate);
TestResultItem *intermediate = new TestResultItem(result);
appendChild(intermediate);
return intermediate;
}
/********************************* TestResultModel *****************************************/ /********************************* TestResultModel *****************************************/
TestResultModel::TestResultModel(QObject *parent) TestResultModel::TestResultModel(QObject *parent)
@@ -135,12 +172,11 @@ TestResultModel::TestResultModel(QObject *parent)
void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoExpand) void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoExpand)
{ {
const QVector<Utils::TreeItem *> &topLevelItems = rootItem()->children(); const int lastRow = rootItem()->childCount() - 1;
const int lastRow = topLevelItems.size() - 1;
if (testResult->result() == Result::MessageCurrentTest) { if (testResult->result() == Result::MessageCurrentTest) {
// MessageCurrentTest should always be the last top level item // MessageCurrentTest should always be the last top level item
if (lastRow >= 0) { if (lastRow >= 0) {
TestResultItem *current = static_cast<TestResultItem *>(topLevelItems.at(lastRow)); TestResultItem *current = static_cast<TestResultItem *>(rootItem()->childAt(lastRow));
const TestResult *result = current->testResult(); const TestResult *result = current->testResult();
if (result && result->result() == Result::MessageCurrentTest) { if (result && result->result() == Result::MessageCurrentTest) {
current->updateDescription(testResult->description()); current->updateDescription(testResult->description());
@@ -158,26 +194,20 @@ void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoEx
m_testResultCount[testResult->result()]++; m_testResultCount[testResult->result()]++;
TestResultItem *newItem = new TestResultItem(testResult); TestResultItem *newItem = new TestResultItem(testResult);
// FIXME this might be totally wrong... we need some more unique information! TestResultItem *parentItem = findParentItemFor(newItem);
if (!testResult->name().isEmpty()) { addFileName(testResult->fileName()); // ensure we calculate the results pane correctly
for (int row = lastRow; row >= 0; --row) { if (parentItem) {
TestResultItem *current = static_cast<TestResultItem *>(topLevelItems.at(row)); parentItem->appendChild(newItem);
const TestResult *result = current->testResult();
if (result && result->name() == testResult->name()) {
current->appendChild(newItem);
if (autoExpand) if (autoExpand)
current->expand(); parentItem->expand();
if (testResult->result() == Result::MessageTestCaseEnd) { if (testResult->result() == Result::MessageTestCaseEnd) {
current->updateResult(); parentItem->updateIntermediateChildren();
emit dataChanged(current->index(), current->index()); parentItem->updateResult();
emit dataChanged(parentItem->index(), parentItem->index());
} }
return; } else {
}
}
}
// if we have a MessageCurrentTest present, add the new top level item before it
if (lastRow >= 0) { if (lastRow >= 0) {
TestResultItem *current = static_cast<TestResultItem *>(topLevelItems.at(lastRow)); TestResultItem *current = static_cast<TestResultItem *>(rootItem()->childAt(lastRow));
const TestResult *result = current->testResult(); const TestResult *result = current->testResult();
if (result && result->result() == Result::MessageCurrentTest) { if (result && result->result() == Result::MessageCurrentTest) {
rootItem()->insertChild(current->index().row(), newItem); rootItem()->insertChild(current->index().row(), newItem);
@@ -186,6 +216,7 @@ void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoEx
} }
// there is no MessageCurrentTest at the last row, but we have a toplevel item - just add it // there is no MessageCurrentTest at the last row, but we have a toplevel item - just add it
rootItem()->appendChild(newItem); rootItem()->appendChild(newItem);
}
} }
void TestResultModel::removeCurrentTestMessage() void TestResultModel::removeCurrentTestMessage()
@@ -205,7 +236,7 @@ void TestResultModel::clearTestResults()
clear(); clear();
m_testResultCount.clear(); m_testResultCount.clear();
m_disabled = 0; m_disabled = 0;
m_processedIndices.clear(); m_fileNames.clear();
m_maxWidthOfFileName = 0; m_maxWidthOfFileName = 0;
m_widthOfLineNumber = 0; m_widthOfLineNumber = 0;
} }
@@ -218,38 +249,28 @@ const TestResult *TestResultModel::testResult(const QModelIndex &idx)
return 0; return 0;
} }
void TestResultModel::recalculateMaxWidthOfFileName(const QFont &font)
{
const QFontMetrics fm(font);
m_maxWidthOfFileName = 0;
for (const QString &fileName : m_fileNames) {
int pos = fileName.lastIndexOf('/');
m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(fileName.mid(pos + 1)));
}
}
void TestResultModel::addFileName(const QString &fileName)
{
const QFontMetrics fm(m_measurementFont);
int pos = fileName.lastIndexOf('/');
m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(fileName.mid(pos + 1)));
m_fileNames.insert(fileName);
}
int TestResultModel::maxWidthOfFileName(const QFont &font) int TestResultModel::maxWidthOfFileName(const QFont &font)
{ {
if (font != m_measurementFont) { if (font != m_measurementFont)
m_processedIndices.clear(); recalculateMaxWidthOfFileName(font);
m_maxWidthOfFileName = 0;
m_measurementFont = font;
}
const QFontMetrics fm(font);
const QVector<Utils::TreeItem *> &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<Utils::TreeItem *> &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<TestResultItem *>(children.at(childRow));
if (const TestResult *result = item->testResult()) {
QString fileName = result->fileName();
const int pos = fileName.lastIndexOf('/');
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; return m_maxWidthOfFileName;
} }
@@ -263,6 +284,44 @@ int TestResultModel::maxWidthOfLineNumber(const QFont &font)
return m_widthOfLineNumber; return m_widthOfLineNumber;
} }
TestResultItem *TestResultModel::findParentItemFor(const TestResultItem *item,
const TestResultItem *startItem) const
{
QTC_ASSERT(item, return nullptr);
TestResultItem *root = startItem ? const_cast<TestResultItem *>(startItem) : nullptr;
const TestResult *result = item->testResult();
const QString &name = result->name();
if (root == nullptr) {
for (int row = rootItem()->childCount() - 1; row >= 0; --row) {
TestResultItem *tmp = static_cast<TestResultItem *>(rootItem()->childAt(row));
if (tmp->testResult()->name() == name) {
root = tmp;
break;
}
}
}
if (root == nullptr)
return root;
bool needsIntermediate = false;
auto predicate = [result, &needsIntermediate](Utils::TreeItem *it) {
TestResultItem *currentItem = static_cast<TestResultItem *>(it);
return currentItem->testResult()->isDirectParentOf(result, &needsIntermediate);
};
TestResultItem *parent = static_cast<TestResultItem *>(root->findAnyChild(predicate));
if (parent) {
if (needsIntermediate) {
// check if the intermediate is present already
if (TestResultItem *intermediate = parent->intermediateFor(item))
return intermediate;
return parent->createAndAddIntermediateFor(item);
}
return parent;
}
return root;
}
/********************************** Filter Model **********************************/ /********************************** Filter Model **********************************/
TestResultFilterModel::TestResultFilterModel(TestResultModel *sourceModel, QObject *parent) TestResultFilterModel::TestResultFilterModel(TestResultModel *sourceModel, QObject *parent)
@@ -279,7 +338,7 @@ void TestResultFilterModel::enableAllResultTypes()
<< Result::UnexpectedPass << Result::Skip << Result::MessageDebug << Result::UnexpectedPass << Result::Skip << Result::MessageDebug
<< Result::MessageWarn << Result::MessageInternal << Result::MessageWarn << Result::MessageInternal
<< Result::MessageFatal << Result::Invalid << Result::BlacklistedPass << Result::MessageFatal << Result::Invalid << Result::BlacklistedPass
<< Result::BlacklistedFail << Result::Benchmark << Result::BlacklistedFail << Result::Benchmark << Result::MessageIntermediate
<< Result::MessageCurrentTest << Result::MessageTestCaseStart << Result::MessageCurrentTest << Result::MessageTestCaseStart
<< Result::MessageTestCaseSuccess << Result::MessageTestCaseWarn << Result::MessageTestCaseSuccess << Result::MessageTestCaseWarn
<< Result::MessageTestCaseFail << Result::MessageTestCaseEnd << Result::MessageTestCaseFail << Result::MessageTestCaseEnd
@@ -337,15 +396,19 @@ bool TestResultFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &s
} }
} }
bool TestResultFilterModel::acceptTestCaseResult(const QModelIndex &index) const bool TestResultFilterModel::acceptTestCaseResult(const QModelIndex &srcIndex) const
{ {
Utils::TreeItem *item = m_sourceModel->itemForIndex(index); for (int row = 0, count = m_sourceModel->rowCount(srcIndex); row < count; ++row) {
QTC_ASSERT(item, return false); const QModelIndex &child = srcIndex.child(row, 0);
Result::Type type = m_sourceModel->testResult(child)->result();
for (const Utils::TreeItem *child : item->children()) { if (type == Result::MessageTestCaseSuccess)
const TestResultItem *resultItem = static_cast<const TestResultItem *>(child); type = Result::Pass;
if (m_enabled.contains(resultItem->testResult()->result())) if (type == Result::MessageTestCaseFail || type == Result::MessageTestCaseWarn) {
if (acceptTestCaseResult(child))
return true; return true;
} else if (m_enabled.contains(type)) {
return true;
}
} }
return false; return false;
} }

View File

@@ -46,6 +46,10 @@ public:
const TestResult *testResult() const { return m_testResult.data(); } const TestResult *testResult() const { return m_testResult.data(); }
void updateDescription(const QString &description); void updateDescription(const QString &description);
void updateResult(); void updateResult();
void updateIntermediateChildren();
TestResultItem *intermediateFor(const TestResultItem *item) const;
TestResultItem *createAndAddIntermediateFor(const TestResultItem *child);
private: private:
TestResultPtr m_testResult; TestResultPtr m_testResult;
@@ -69,11 +73,15 @@ public:
int disabledTests() const { return m_disabled; } int disabledTests() const { return m_disabled; }
private: private:
void recalculateMaxWidthOfFileName(const QFont &font);
void addFileName(const QString &fileName);
TestResultItem *findParentItemFor(const TestResultItem *item,
const TestResultItem *startItem = 0) const;
QMap<Result::Type, int> m_testResultCount; QMap<Result::Type, int> m_testResultCount;
int m_widthOfLineNumber = 0; int m_widthOfLineNumber = 0;
int m_maxWidthOfFileName = 0; int m_maxWidthOfFileName = 0;
int m_disabled = 0; int m_disabled = 0;
QList<int> m_processedIndices; QSet<QString> m_fileNames; // TODO: check whether this caching is necessary at all
QFont m_measurementFont; QFont m_measurementFont;
}; };
@@ -93,7 +101,7 @@ protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
private: private:
bool acceptTestCaseResult(const QModelIndex &index) const; bool acceptTestCaseResult(const QModelIndex &srcIndex) const;
TestResultModel *m_sourceModel; TestResultModel *m_sourceModel;
QSet<Result::Type> m_enabled; QSet<Result::Type> m_enabled;
}; };