// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only


#include <QtCore/QCoreApplication>
#include <QTest>

Q_DECLARE_METATYPE(QTest::TestFailMode)

class tst_ExpectFail: public QObject
{
    Q_OBJECT

private slots:
    void cleanupTestCase() const;
    void init() const;
    void xfailAndContinue() const;
    void xfailAndAbort() const;
    void xfailContinueSkip() const;
    void xfailAbortSkip() const;
    void xfailTwice() const;
    void xfailDataDrivenTwice() const;
    void xfailDataDrivenTwice_data() const { xfailDataDriven_data(false); }
    void xfailWithQString() const;
    void xfailDataDrivenWithQString_data() const { xfailDataDriven_data(false); }
    void xfailDataDrivenWithQString() const;
    void xfailDataDrivenWithQVerify_data() const { xfailDataDriven_data(false); }
    void xfailDataDrivenWithQVerify() const;
    void xfailDataDrivenWithQCompare_data() const { xfailDataDriven_data(false); }
    void xfailDataDrivenWithQCompare() const;
    void xfailOnWrongRow_data() const { xfailDataDriven_data(true); }
    void xfailOnWrongRow() const;
    void xfailOnAnyRow_data() const { xfailDataDriven_data(true); }
    void xfailOnAnyRow() const;
    void xfailWithoutCheck_data() const { xfailDataDriven_data(true); }
    void xfailWithoutCheck() const;
    void xpassAbort() const;
    void xpassAbortSkip() const;
    void xpassAbortXfailContinue() const;
    void xpassContinue() const;
    void xpassContinueSkip() const;
    void xpassContinueXfailAbort() const;
    void xpassAbortDataDrivenWithQVerify_data() const { xpassDataDriven_data(); }
    void xpassAbortDataDrivenWithQVerify() const;
    void xpassContinueDataDrivenWithQVerify_data() const { xpassDataDriven_data(); }
    void xpassContinueDataDrivenWithQVerify() const;
    void xpassAbortDataDrivenWithQCompare_data() const { xpassDataDriven_data(); }
    void xpassAbortDataDrivenWithQCompare() const;
    void xpassContinueDataDrivenWithQCompare_data() const { xpassDataDriven_data(); }
    void xpassContinueDataDrivenWithQCompare() const;

private:
    void xfailDataDriven_data(bool failOnly) const;
    void xpassDataDriven_data() const;
};
static int casesTested = 2;
// What the totals line's numbers *should* be:
static int passed = 2, failed = 0, skipped = 0;
// Total and passed get {init,cleanup}TestCase() in addition to the actual tests.

void tst_ExpectFail::init() const
{
    ++casesTested;
}

void tst_ExpectFail::cleanupTestCase() const
{
    qDebug("Totals should add up to %d: %d passed, %d failed, %d skipped",
           casesTested, passed, failed, skipped);
}

void tst_ExpectFail::xfailAndContinue() const
{
    ++passed;
    qDebug("begin");
    QEXPECT_FAIL("", "This should xfail", Continue);
    QVERIFY(false);
    qDebug("after");
}

void tst_ExpectFail::xfailAndAbort() const
{
    ++passed;
    qDebug("begin");
    QEXPECT_FAIL("", "This should xfail", Abort);
    QVERIFY(false);

    // If we get here the test did not correctly abort on the previous QVERIFY.
    QVERIFY2(false, "This should not be reached");
}

void tst_ExpectFail::xfailContinueSkip() const
{
    ++skipped;
    QEXPECT_FAIL("", "This should xfail then skip", Continue);
    QVERIFY(false);
    QSKIP("This skip should be reported and counted");
}

void tst_ExpectFail::xfailAbortSkip() const
{
    ++passed;
    QEXPECT_FAIL("", "This should xfail", Abort);
    QVERIFY(false);

    // If we get here the test did not correctly abort on the previous QVERIFY.
    QSKIP("This skip should not be reached");
}

void tst_ExpectFail::xfailTwice() const
{
    ++failed;
    QEXPECT_FAIL("", "Calling QEXPECT_FAIL once is fine", Continue);
    QEXPECT_FAIL("", "Calling QEXPECT_FAIL when already expecting a failure is "
                     "an error and should abort this test function", Continue);

    // If we get here the test did not correctly abort on the double call to QEXPECT_FAIL.
    QVERIFY2(false, "This should not be reached");
}

void tst_ExpectFail::xfailDataDrivenTwice() const
{
    ++failed;
    // Same with data-driven cases (twist semantics of unused shouldPass; we
    // have four combinations to test):
    QEXPECT_FAIL("Pass Abort", "Calling QEXPECT_FAIL once on a test-case is fine", Abort);
    QEXPECT_FAIL("Pass Abort", "Calling QEXPECT_FAIL when already expecting a failure is "
                               "an error and should abort this test function", Continue);
    QEXPECT_FAIL("Fail Abort", "Calling QEXPECT_FAIL once on a test-case is fine", Abort);
    QEXPECT_FAIL("Fail Abort", "Calling QEXPECT_FAIL when already expecting a failure is "
                               "an error and should abort this test function", Abort);
    QEXPECT_FAIL("Pass Continue", "Calling QEXPECT_FAIL once on a test-case is fine", Continue);
    QEXPECT_FAIL("Pass Continue", "Calling QEXPECT_FAIL when already expecting a failure is "
                                  "an error and should abort this test function", Abort);
    QEXPECT_FAIL("Fail Continue", "Calling QEXPECT_FAIL once on a test-case is fine", Continue);
    QEXPECT_FAIL("Fail Continue", "Calling QEXPECT_FAIL when already expecting a failure is "
                                  "an error and should abort this test function", Continue);

    // If we get here the test did not correctly abort on the double call to QEXPECT_FAIL.
    QVERIFY2(false, "This should not be reached");
}

void tst_ExpectFail::xfailWithQString() const
{
    ++passed;
    QEXPECT_FAIL("", QString("A string").toLatin1().constData(), Continue);
    QVERIFY(false);

    int bugNo = 5;
    QLatin1String msg("The message");
    QEXPECT_FAIL("", QString("Bug %1 (%2)").arg(bugNo).arg(msg).toLatin1().constData(), Continue);
    QVERIFY(false);
}

void tst_ExpectFail::xfailDataDrivenWithQString() const
{
    // This test does not (yet) distinguish the two Pass cases.
    QFETCH(bool, shouldPass);
    QFETCH(QTest::TestFailMode, failMode);
    if (shouldPass || failMode == QTest::Continue)
        ++skipped;
    else
        ++passed;

    QEXPECT_FAIL("Fail Abort", QString("A string").toLatin1().constData(), Abort);
    QEXPECT_FAIL("Fail Continue", QString("A string").toLatin1().constData(), Continue);
    // TODO: why aren't QVERIFY2()'s smessages seen ?
    QVERIFY2(shouldPass, "Both Fail cases should XFAIL here");
    // Fail Abort should be gone now.

    int bugNo = 5;
    QLatin1String msg("The message");
    QEXPECT_FAIL("Fail Continue", qPrintable(QString("Bug %1 (%2)").arg(bugNo).arg(msg)), Continue);
    QVERIFY2(shouldPass, "Only Fail Continue should see this");

    // FAIL is a pass, and SKIP trumps pass.
    QSKIP("Each Continue or Pass reports this and increments skip-count");
}

void tst_ExpectFail::xfailDataDrivenWithQVerify() const
{
    // This test does not (yet) distinguish the two Pass cases.
    ++passed;
    QFETCH(bool, shouldPass);
    QFETCH(QTest::TestFailMode, failMode);

    QEXPECT_FAIL("Fail Abort", "This test should xfail", Abort);
    QEXPECT_FAIL("Fail Continue", "This test should xfail", Continue);
    QVERIFY2(shouldPass, "Both Fail cases should XFAIL here");

    // If we get here, either we expected to pass or we expected to
    // fail and the failure mode was Continue.
    if (!shouldPass)
        QCOMPARE(failMode, QTest::Continue);
}

void tst_ExpectFail::xfailDataDriven_data(bool failOnly) const
{
    QTest::addColumn<bool>("shouldPass");
    QTest::addColumn<QTest::TestFailMode>("failMode");

    if (!failOnly) {
        QTest::newRow("Pass Abort")    << true  << QTest::Abort;
        QTest::newRow("Pass Continue") << true  << QTest::Continue;
    }
    QTest::newRow("Fail Abort")    << false << QTest::Abort;
    QTest::newRow("Fail Continue") << false << QTest::Continue;
}

void tst_ExpectFail::xfailDataDrivenWithQCompare() const
{
    // This test does not (yet) distinguish the two Pass cases.
    ++passed;
    QFETCH(bool, shouldPass);
    QFETCH(QTest::TestFailMode, failMode);

    QEXPECT_FAIL("Fail Abort", "This test should xfail", Abort);
    QEXPECT_FAIL("Fail Continue", "This test should xfail", Continue);

    QCOMPARE(1, shouldPass ? 1 : 2);

    // If we get here, either we expected to pass or we expected to
    // fail and the failure mode was Continue.
    if (!shouldPass)
        QCOMPARE(failMode, QTest::Continue);
}

void tst_ExpectFail::xfailOnWrongRow() const
{
    ++passed;
    qDebug("Should pass (*not* xpass), despite test-case name");
    // QEXPECT_FAIL for a row that does not exist should be ignored.
    // (It might be conditional data(), so exist in other circumstances.)
    QFETCH(QTest::TestFailMode, failMode);
    // You can't pass a variable as the last parameter of QEXPECT_FAIL,
    // because the macro adds "QTest::" in front of the last parameter.
    // That is why the following code appears to be a little strange.
    if (failMode == QTest::Abort)
        QEXPECT_FAIL("wrong row", "This xfail should be ignored", Abort);
    else
        QEXPECT_FAIL("wrong row", "This xfail should be ignored", Continue);
    QTEST(false, "shouldPass"); // _data skips the passing tests as pointless
}

void tst_ExpectFail::xfailOnAnyRow() const
{
    ++passed;
    // In a data-driven test, passing an empty first parameter to QEXPECT_FAIL
    // should mean that the failure is expected for all data rows.
    QFETCH(QTest::TestFailMode, failMode);
    // You can't pass a variable as the last parameter of QEXPECT_FAIL,
    // because the macro adds "QTest::" in front of the last parameter.
    // That is why the following code appears to be a little strange.
    if (failMode == QTest::Abort)
        QEXPECT_FAIL("", "This test should xfail", Abort);
    else
        QEXPECT_FAIL("", "This test should xfail", Continue);
    QTEST(true, "shouldPass"); // _data skips the passing tests as pointless
}

void tst_ExpectFail::xfailWithoutCheck() const
{
    ++failed;
    qDebug("Should fail (*not* xfail), despite test-case name");
    QTEST(false, "shouldPass"); // _data skips the passing tests as pass/fail is irrelevant
    QEXPECT_FAIL("Fail Abort", "Calling QEXPECT_FAIL without any subsequent check is an error",
                 Abort);
    QEXPECT_FAIL("Fail Continue", "Calling QEXPECT_FAIL without any subsequent check is an error",
                 Continue);
}

void tst_ExpectFail::xpassAbort() const
{
    ++failed;
    QEXPECT_FAIL("", "This test should xpass", Abort);
    QVERIFY(true);

    // If we get here the test did not correctly abort on the previous
    // unexpected pass.
    QVERIFY2(false, "This should not be reached");
}

void tst_ExpectFail::xpassAbortSkip() const
{
    ++failed;
    QEXPECT_FAIL("", "This test should xpass", Abort);
    QVERIFY(true);

    QSKIP("This should not be reached (and not add to skip-count)");
}

void tst_ExpectFail::xpassAbortXfailContinue() const
{
    ++failed;
    QEXPECT_FAIL("", "This test should xpass", Abort);
    QVERIFY(true);

    // If we get here the test did not correctly abort on the previous
    // unexpected pass.
    QEXPECT_FAIL("", "This should not be reached", Continue);
    QVERIFY2(false, "This should not be reached");
}

void tst_ExpectFail::xpassContinue() const
{
    ++failed;
    QEXPECT_FAIL("", "This test should xpass", Continue);
    QVERIFY(true);
    qDebug("This should be reached");
}

void tst_ExpectFail::xpassDataDriven_data() const
{
    QTest::addColumn<bool>("shouldXPass");

    QTest::newRow("XPass")  << true;
    QTest::newRow("Pass")   << false;
}

void tst_ExpectFail::xpassContinueSkip() const
{
    ++failed; // and *not* ++skipped
    QEXPECT_FAIL("", "This test should xpass", Continue);
    QVERIFY(true);
    QSKIP("This should be reached but not increment skip-count");
}

void tst_ExpectFail::xpassContinueXfailAbort() const
{
    ++failed;
    QEXPECT_FAIL("", "This test should xpass", Continue);
    QVERIFY(true);
    QEXPECT_FAIL("", "This test should xfail but not add to totals", Abort);
    QVERIFY(false);
    QVERIFY2(false, "This should not be reached");
}

void tst_ExpectFail::xpassAbortDataDrivenWithQVerify() const
{
    QFETCH(bool, shouldXPass);
    if (shouldXPass)
        ++failed;
    else
        ++passed;

    QEXPECT_FAIL("XPass", "This test-row should xpass", Abort);
    QVERIFY(true);

    // We should only get here if the test wasn't supposed to xpass.
    QVERIFY2(!shouldXPass, "Test failed to Abort on XPASS");
}

void tst_ExpectFail::xpassContinueDataDrivenWithQVerify() const
{
    QFETCH(bool, shouldXPass);
    if (shouldXPass)
        ++failed;
    else
        ++passed;

    QEXPECT_FAIL("XPass", "This test-row should xpass", Continue);
    QVERIFY(true);

    qDebug(shouldXPass ? "Test should Continue past XPASS" : "Test should simply PASS");
}

void tst_ExpectFail::xpassAbortDataDrivenWithQCompare() const
{
    QFETCH(bool, shouldXPass);
    if (shouldXPass)
        ++failed;
    else
        ++passed;

    QEXPECT_FAIL("XPass", "This test should xpass", Abort);
    QCOMPARE(1, 1);

    // We should only get here if the test wasn't supposed to xpass.
    QVERIFY2(!shouldXPass, "Test failed to Abort on XPASS");
}

void tst_ExpectFail::xpassContinueDataDrivenWithQCompare() const
{
    QFETCH(bool, shouldXPass);
    if (shouldXPass)
        ++failed;
    else
        ++passed;

    QEXPECT_FAIL("XPass", "This test should xpass", Continue);
    QCOMPARE(1, 1);

    qDebug(shouldXPass ? "Test should Continue past XPASS" : "Test should simply PASS");
}

QTEST_MAIN(tst_ExpectFail)
#include "tst_expectfail.moc"
