testresultmodel.cpp 14.5 KB
Newer Older
1 2
/****************************************************************************
**
Christian Stenger's avatar
Christian Stenger committed
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
5
**
Christian Stenger's avatar
Christian Stenger committed
6
** This file is part of Qt Creator.
7
**
Christian Stenger's avatar
Christian Stenger committed
8 9 10
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
11
** Software or, alternatively, in accordance with the terms contained in
Christian Stenger's avatar
Christian Stenger committed
12 13 14
** 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.
15
**
Christian Stenger's avatar
Christian Stenger committed
16 17 18 19 20 21 22
** 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.
23 24 25
**
****************************************************************************/

26
#include "autotesticons.h"
27
#include "testresultdelegate.h"
28 29
#include "testresultmodel.h"

hjk's avatar
hjk committed
30 31
#include <utils/qtcassert.h>

32 33 34 35 36 37
#include <QFontMetrics>
#include <QIcon>

namespace Autotest {
namespace Internal {

38
/********************************* TestResultItem ******************************************/
39

40
TestResultItem::TestResultItem(const TestResultPtr &testResult)
41
    : m_testResult(testResult)
42 43 44
{
}

45
TestResultItem::~TestResultItem()
46 47 48
{
}

49
static QIcon testResultIcon(Result::Type result) {
50 51 52 53 54 55 56 57 58 59 60 61 62 63
    const static QIcon icons[] = {
        Icons::RESULT_PASS.icon(),
        Icons::RESULT_FAIL.icon(),
        Icons::RESULT_XFAIL.icon(),
        Icons::RESULT_XPASS.icon(),
        Icons::RESULT_SKIP.icon(),
        Icons::RESULT_BLACKLISTEDPASS.icon(),
        Icons::RESULT_BLACKLISTEDFAIL.icon(),
        Icons::RESULT_BENCHMARK.icon(),
        Icons::RESULT_MESSAGEDEBUG.icon(),
        Icons::RESULT_MESSAGEDEBUG.icon(), // Info gets the same handling as Debug for now
        Icons::RESULT_MESSAGEWARN.icon(),
        Icons::RESULT_MESSAGEFATAL.icon(),
        Icons::RESULT_MESSAGEFATAL.icon(), // System gets same handling as Fatal for now
64
    }; // provide an icon for unknown??
65

Christian Stenger's avatar
Christian Stenger committed
66
    if (result < 0 || result >= Result::MessageInternal) {
67
        switch (result) {
Christian Stenger's avatar
Christian Stenger committed
68 69 70 71 72 73
        case Result::MessageTestCaseSuccess:
            return icons[Result::Pass];
        case Result::MessageTestCaseFail:
            return icons[Result::Fail];
        case Result::MessageTestCaseWarn:
            return icons[Result::MessageWarn];
74 75 76 77
        default:
            return QIcon();
        }
    }
78 79 80
    return icons[result];
}

81
QVariant TestResultItem::data(int column, int role) const
82
{
83 84
    switch (role) {
    case Qt::DecorationRole:
85
        return m_testResult ? testResultIcon(m_testResult->result()) : QVariant();
86
    case Qt::DisplayRole:
87
        return m_testResult ? m_testResult->outputString(true) : QVariant();
88 89 90
    default:
        return Utils::TreeItem::data(column, role);
    }
91 92 93 94 95 96 97 98 99 100 101
}

void TestResultItem::updateDescription(const QString &description)
{
    QTC_ASSERT(m_testResult, return);

    m_testResult->setDescription(description);
}

void TestResultItem::updateResult()
{
102
    if (!TestResult::isMessageCaseStart(m_testResult->result()))
103 104
        return;

Christian Stenger's avatar
Christian Stenger committed
105
    Result::Type newResult = Result::MessageTestCaseSuccess;
hjk's avatar
hjk committed
106
    for (Utils::TreeItem *child : *this) {
107 108 109
        const TestResult *current = static_cast<TestResultItem *>(child)->testResult();
        if (current) {
            switch (current->result()) {
Christian Stenger's avatar
Christian Stenger committed
110 111 112
            case Result::Fail:
            case Result::MessageFatal:
            case Result::UnexpectedPass:
113
            case Result::MessageTestCaseFail:
Christian Stenger's avatar
Christian Stenger committed
114
                m_testResult->setResult(Result::MessageTestCaseFail);
115
                return;
Christian Stenger's avatar
Christian Stenger committed
116 117 118 119 120
            case Result::ExpectedFail:
            case Result::MessageWarn:
            case Result::Skip:
            case Result::BlacklistedFail:
            case Result::BlacklistedPass:
121
            case Result::MessageTestCaseWarn:
Christian Stenger's avatar
Christian Stenger committed
122
                newResult = Result::MessageTestCaseWarn;
123 124 125
                break;
            default: {}
            }
126 127
        }
    }
128 129
    m_testResult->setResult(newResult);
}
130

131 132
void TestResultItem::updateIntermediateChildren()
{
hjk's avatar
hjk committed
133
    for (Utils::TreeItem *child : *this) {
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
        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;
    }
Christian Stenger's avatar
Christian Stenger committed
152
    return nullptr;
153 154 155 156 157
}

TestResultItem *TestResultItem::createAndAddIntermediateFor(const TestResultItem *child)
{
    TestResultPtr result(m_testResult->createIntermediateResultFor(child->testResult()));
Christian Stenger's avatar
Christian Stenger committed
158
    QTC_ASSERT(!result.isNull(), return nullptr);
159 160 161 162 163 164
    result->setResult(Result::MessageIntermediate);
    TestResultItem *intermediate = new TestResultItem(result);
    appendChild(intermediate);
    return intermediate;
}

165 166 167
/********************************* TestResultModel *****************************************/

TestResultModel::TestResultModel(QObject *parent)
168
    : Utils::TreeModel<>(parent)
169
{
170 171
}

172
void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoExpand)
173
{
174
    const int lastRow = rootItem()->childCount() - 1;
175
    if (testResult->result() == Result::MessageCurrentTest) {
Christian Stenger's avatar
Christian Stenger committed
176
        // MessageCurrentTest should always be the last top level item
177
        if (lastRow >= 0) {
178
            TestResultItem *current = static_cast<TestResultItem *>(rootItem()->childAt(lastRow));
179
            const TestResult *result = current->testResult();
Christian Stenger's avatar
Christian Stenger committed
180
            if (result && result->result() == Result::MessageCurrentTest) {
181 182 183 184 185 186
                current->updateDescription(testResult->description());
                emit dataChanged(current->index(), current->index());
                return;
            }
        }

187
        rootItem()->appendChild(new TestResultItem(testResult));
188
        return;
189 190
    }

191 192 193 194
    if (testResult->result() == Result::MessageDisabledTests)
        m_disabled += testResult->line();
    m_testResultCount[testResult->result()]++;

195
    TestResultItem *newItem = new TestResultItem(testResult);
196 197 198 199 200 201 202
    TestResultItem *parentItem = findParentItemFor(newItem);
    addFileName(testResult->fileName()); // ensure we calculate the results pane correctly
    if (parentItem) {
        parentItem->appendChild(newItem);
        if (autoExpand)
            parentItem->expand();
        if (testResult->result() == Result::MessageTestCaseEnd) {
203 204 205 206 207
            if (parentItem->childCount()) {
                parentItem->updateIntermediateChildren();
                emit dataChanged(parentItem->firstChild()->index(),
                                 parentItem->lastChild()->index());
            }
208 209 210 211 212 213
            parentItem->updateResult();
            emit dataChanged(parentItem->index(), parentItem->index());
        }
    } else {
        if (lastRow >= 0) {
            TestResultItem *current = static_cast<TestResultItem *>(rootItem()->childAt(lastRow));
214
            const TestResult *result = current->testResult();
215 216
            if (result && result->result() == Result::MessageCurrentTest) {
                rootItem()->insertChild(current->index().row(), newItem);
217
                return;
218 219
            }
        }
220 221
        // there is no MessageCurrentTest at the last row, but we have a toplevel item - just add it
        rootItem()->appendChild(newItem);
222
    }
223 224
}

225 226
void TestResultModel::removeCurrentTestMessage()
{
hjk's avatar
hjk committed
227
    std::vector<Utils::TreeItem *> topLevelItems(rootItem()->begin(), rootItem()->end());
228 229 230
    auto end = topLevelItems.rend();
    for (auto it = topLevelItems.rbegin(); it != end; ++it) {
        TestResultItem *current = static_cast<TestResultItem *>(*it);
Christian Stenger's avatar
Christian Stenger committed
231
        if (current->testResult()->result() == Result::MessageCurrentTest) {
232
            destroyItem(current);
233 234
            break;
        }
235 236 237
    }
}

238 239
void TestResultModel::clearTestResults()
{
240
    clear();
241
    m_testResultCount.clear();
242
    m_disabled = 0;
243
    m_fileNames.clear();
244 245
    m_maxWidthOfFileName = 0;
    m_widthOfLineNumber = 0;
246 247
}

248
const TestResult *TestResultModel::testResult(const QModelIndex &idx)
249
{
250
    if (idx.isValid())
251
        return static_cast<TestResultItem *>(itemForIndex(idx))->testResult();
252

253
    return 0;
254 255
}

256
void TestResultModel::recalculateMaxWidthOfFileName(const QFont &font)
257
{
258
    const QFontMetrics fm(font);
259 260 261 262
    m_maxWidthOfFileName = 0;
    for (const QString &fileName : m_fileNames) {
        int pos = fileName.lastIndexOf('/');
        m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(fileName.mid(pos + 1)));
263
    }
264 265 266 267 268 269 270 271 272 273 274 275 276 277
}

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)
{
    if (font != m_measurementFont)
        recalculateMaxWidthOfFileName(font);
278 279 280 281 282 283 284 285
    return m_maxWidthOfFileName;
}

int TestResultModel::maxWidthOfLineNumber(const QFont &font)
{
    if (m_widthOfLineNumber == 0 || font != m_measurementFont) {
        QFontMetrics fm(font);
        m_measurementFont = font;
286
        m_widthOfLineNumber = fm.width("88888");
287 288 289 290
    }
    return m_widthOfLineNumber;
}

291 292 293 294 295 296 297 298
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();

299
    if (root == nullptr && !name.isEmpty()) {
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
        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);
    };
316
    TestResultItem *parent = static_cast<TestResultItem *>(root->reverseFindAnyChild(predicate));
317 318 319 320 321 322 323 324 325 326 327 328
    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;
}

329 330 331 332 333 334 335 336 337 338 339 340
/********************************** Filter Model **********************************/

TestResultFilterModel::TestResultFilterModel(TestResultModel *sourceModel, QObject *parent)
    : QSortFilterProxyModel(parent),
      m_sourceModel(sourceModel)
{
    setSourceModel(sourceModel);
    enableAllResultTypes();
}

void TestResultFilterModel::enableAllResultTypes()
{
Christian Stenger's avatar
Christian Stenger committed
341 342 343 344
    m_enabled << Result::Pass << Result::Fail << Result::ExpectedFail
              << Result::UnexpectedPass << Result::Skip << Result::MessageDebug
              << Result::MessageWarn << Result::MessageInternal
              << Result::MessageFatal << Result::Invalid << Result::BlacklistedPass
345
              << Result::BlacklistedFail << Result::Benchmark << Result::MessageIntermediate
Christian Stenger's avatar
Christian Stenger committed
346 347
              << Result::MessageCurrentTest << Result::MessageTestCaseStart
              << Result::MessageTestCaseSuccess << Result::MessageTestCaseWarn
348
              << Result::MessageTestCaseFail << Result::MessageTestCaseEnd
349
              << Result::MessageInfo << Result::MessageSystem;
350 351 352
    invalidateFilter();
}

353
void TestResultFilterModel::toggleTestResultType(Result::Type type)
354 355 356
{
    if (m_enabled.contains(type)) {
        m_enabled.remove(type);
Christian Stenger's avatar
Christian Stenger committed
357 358
        if (type == Result::MessageInternal)
            m_enabled.remove(Result::MessageTestCaseEnd);
359 360
        if (type == Result::MessageDebug)
            m_enabled.remove(Result::MessageInfo);
361 362
    } else {
        m_enabled.insert(type);
Christian Stenger's avatar
Christian Stenger committed
363 364
        if (type == Result::MessageInternal)
            m_enabled.insert(Result::MessageTestCaseEnd);
365 366
        if (type == Result::MessageDebug)
            m_enabled.insert(Result::MessageInfo);
367 368 369 370 371 372 373 374 375 376 377 378 379 380
    }
    invalidateFilter();
}

void TestResultFilterModel::clearTestResults()
{
    m_sourceModel->clearTestResults();
}

bool TestResultFilterModel::hasResults()
{
    return rowCount(QModelIndex());
}

381
const TestResult *TestResultFilterModel::testResult(const QModelIndex &index) const
382 383 384 385 386 387 388 389 390
{
    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;
391 392 393 394 395 396 397 398 399 400 401 402
    Result::Type resultType = m_sourceModel->testResult(index)->result();
    switch (resultType) {
    case Result::MessageTestCaseSuccess:
        return m_enabled.contains(Result::Pass);
    case Result::MessageTestCaseFail:
    case Result::MessageTestCaseWarn:
        return acceptTestCaseResult(index);
    default:
        return m_enabled.contains(resultType);
    }
}

403
bool TestResultFilterModel::acceptTestCaseResult(const QModelIndex &srcIndex) const
404
{
405 406 407 408 409 410 411 412 413
    for (int row = 0, count = m_sourceModel->rowCount(srcIndex); row < count; ++row) {
        const QModelIndex &child = srcIndex.child(row, 0);
        Result::Type type = m_sourceModel->testResult(child)->result();
        if (type == Result::MessageTestCaseSuccess)
            type = Result::Pass;
        if (type == Result::MessageTestCaseFail || type == Result::MessageTestCaseWarn) {
            if (acceptTestCaseResult(child))
                return true;
        } else if (m_enabled.contains(type)) {
414
            return true;
415
        }
416 417
    }
    return false;
418 419
}

420 421
} // namespace Internal
} // namespace Autotest