Split the giant test binaries into smaller unit tests

This is an intermediate step to have smaller unit tests. It is a trade off
between having many many test executables compared to a few bigger ones. But
this comes a lot closer to what QtTest is meant to be used.
This commit is contained in:
Roland Winklmeier
2018-08-22 13:22:01 +02:00
parent a19ccabf35
commit 77ce9f8b8a
122 changed files with 1884 additions and 2422 deletions

View File

@@ -0,0 +1,124 @@
/* Copyright (C) 2013
* swift Project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
//! \cond PRIVATE_TESTS
/*!
* \file
* \ingroup testblackcore
*/
#include "expect.h"
#include "blackmisc/range.h"
#include <QCoreApplication>
#include <QEventLoop>
#include <QTest>
#include <QTimer>
namespace BlackCoreTest
{
void ExpectUnit::wait(int timeout)
{
m_parent->wait(m_srcloc, timeout, *this);
}
/* Connect slot for the first expectation, then send all queued signals.
*/
void ExpectUnit::init() const
{
m_nextExpect = m_expects.begin();
next();
for (auto i = m_sends.begin(); i != m_sends.end(); ++i)
{
(*i)();
}
}
/* Connect slot for the next expectation, or call the onDone() callback if that was the last one.
* Each expectation slot calls this function, so each successful expectation connects the next one.
*/
void ExpectUnit::next() const
{
m_guard.cleanup();
if (m_nextExpect == m_expects.end())
{
if (m_onDone)
{
m_onDone(this);
}
}
else
{
(*m_nextExpect)();
++m_nextExpect;
}
}
/* For each unit queued by an overloaded wait(), setup its onDone() callback and call its init() method.
* The onDone() callback removes the unit from the queue. When all units have been removed from the queue,
* then the expectation has succeeded. If the timer times out before that happens, then the expectation
* has failed. Failures cause subsequent calls to wait() to do nothing.
*/
void Expect::wait(const SourceLocation& srcloc, int timeout)
{
auto unitsCopy = m_units;
m_units.clear();
if (m_failed)
{
return;
}
for (auto i : unitsCopy)
{
i->onDone([&](const ExpectUnit* u){ unitsCopy.remove(u); });
}
// toList is an easy way to make a temporary copy, needed because init might invalidate iterators
for (auto i : unitsCopy.toList()) // clazy:exclude=container-anti-pattern,range-loop
{
i->init();
}
QTimer timer;
timer.setSingleShot(true);
QObject::connect(&timer, &QTimer::timeout, [=, &unitsCopy]{
reportTimeout(srcloc, unitsCopy);
for (auto i : BlackMisc::as_const(unitsCopy))
{
i->onDone(nullptr); //paranoia
}
unitsCopy.clear();
m_failed = true;
});
timer.start(timeout * 1000);
while (! unitsCopy.isEmpty())
{
QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 10);
}
}
void Expect::reportTimeout(const SourceLocation& srcloc, const QSet<const ExpectUnit*>& units)
{
QString msg = "*** Timed Out ***";
QString prefix = "\nwhile waiting for ";
for (auto i = units.begin(); i != units.end(); ++i)
{
msg += prefix + (*i)->m_waitingFor;
prefix = "\nand ";
}
QTest::qFail(qPrintable(msg), qPrintable(srcloc.file), srcloc.line);
}
//! \endcond
} //namespace

View File

@@ -0,0 +1,300 @@
/* Copyright (C) 2013
* swift Project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#ifndef BLACKCORETEST_EXPECT_H
#define BLACKCORETEST_EXPECT_H
#include <QMetaMethod>
#include <QMetaObject>
#include <QObject>
#include <QPointer>
#include <QSet>
#include <QString>
#include <QVector>
#include <QtGlobal>
#include <functional>
//! \cond PRIVATE_TESTS
/*!
* \file
* \ingroup testblackcore
*/
namespace BlackCoreTest
{
class Expect;
/*!
* Class representing a position in a source code file, for error reporting. Used in combination with the SOURCE_LOCATION macro.
*/
class SourceLocation
{
public:
SourceLocation(const QString &f, int ln) : file(f), line(ln) {} //!< Constructor
const QString file; //!< Source code filename
const int line; //!< Line number
};
//! Evaluates to an instance of SourceLocation representing the position where the macro is used.
#define SOURCE_LOCATION (BlackCoreTest::SourceLocation(__FILE__, __LINE__))
/*!
* RAII class for Qt signal/slot connections. All connections are closed when object is destroyed.
*/
class ConnectGuard
{
public:
//! Constructor.
ConnectGuard() {}
//! Destructor.
~ConnectGuard() { cleanup(); }
//! Add a connection to the object..
ConnectGuard &operator+= (const QMetaObject::Connection &conn) { m_conns += conn; return *this; }
//! Disconnect and remove all stored connections.
void cleanup() { for (auto i = m_conns.cbegin(); i != m_conns.cend(); ++i) QObject::disconnect(*i); m_conns.clear(); }
//! Copying is only allowed when there are no connections stored.
//! @{
ConnectGuard(const ConnectGuard &other) { Q_ASSERT(other.m_conns.isEmpty()); Q_UNUSED(other); }
ConnectGuard &operator= (const ConnectGuard &other) { Q_ASSERT(other.m_conns.isEmpty()); Q_UNUSED(other); return *this; }
//! @}
private:
QVector<QMetaObject::Connection> m_conns;
};
/*!
* One unit of expectation, as returned by Expect::unit().
*
* Contains a sequence of signals to send, and a sequence of signals that are expected to be received in reply.
*/
class ExpectUnit
{
public:
/*!
* Adds a signal to the list of signals to send, with optional arguments.
* \param slot A pointer-to-member-function of the subject class.
* \return this object, so methods can be chained.
*/
template <class F> ExpectUnit &send(F slot) { m_sends.push_back(std::bind(slot, subject<F>())); return *this; }
/*!
* \copydoc send(F)
* \param arg1
*/
template <class F, class T1> ExpectUnit &send(F slot, T1 arg1) { m_sends.push_back(std::bind(slot, subject<F>(), arg1)); return *this; }
/*!
* \copydoc send(F,T1)
* \param arg2
*/
template <class F, class T1, class T2> ExpectUnit &send(F slot, T1 arg1, T2 arg2) { m_sends.push_back(std::bind(slot, subject<F>(), arg1, arg2)); return *this; }
/*!
* \copydoc send(F,T1,T2)
* \param arg3
*/
template <class F, class T1, class T2, class T3> ExpectUnit &send(F slot, T1 arg1, T2 arg2, T3 arg3) { m_sends.push_back(std::bind(slot, subject<F>(), arg1, arg2, arg3)); return *this; }
/*!
* \copydoc send(F,T1,T2,T3)
* \param arg4
*/
template <class F, class T1, class T2, class T3, class T4> ExpectUnit &send(F slot, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { m_sends.push_back(std::bind(slot, subject<F>(), arg1, arg2, arg3, arg4)); return *this; }
/*!
* \copydoc send(F,T1,T2,T3,T4)
* \param arg5
*/
template <class F, class T1, class T2, class T3, class T4, class T5> ExpectUnit &send(F slot, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { m_sends.push_back(std::bind(slot, subject<F>(), arg1, arg2, arg3, arg4, arg5)); return *this; }
/*!
* Adds a signal to the list of signals which are expects to be received.
* \param signal A pointer-to-member-function of the subject class.
* \param slot A callable object which will be connected to the signal, e.g. to validate the signal arguments.
* \return this object, so methods can be chained.
*/
template <class F1, class F2> ExpectUnit &expect(F1 signal, F2 slot)
{
auto subj = subject<F1>();
auto next = [ = ] { this->next(); };
m_expects.push_back([ = ]
{
m_waitingFor = subj->metaObject()->className();
m_waitingFor += "::";
m_waitingFor += QMetaMethod::fromSignal(signal).name();
m_guard += QObject::connect(subj, signal, slot);
m_guard += QObject::connect(subj, signal, next);
});
return *this;
}
/*!
* Adds a signal to the list of signals which are expected to be received.
* \param signal A pointer-to-member-function of the subject class.
* \return this object, so methods can be chained.
*/
template <class F> ExpectUnit &expect(F signal) { return expect(signal, [] {}); }
/*!
* Sends all the queued signals, then waits for each of the expected signals in sequence.
* If the full sequence of expected signals is not received, calls QTest::qFail().
* \param timeout Time to wait in seconds. Qt event queue is processed when waiting.
*/
void wait(int timeout);
private:
friend class Expect;
ExpectUnit(Expect *parent, QObject *subject, const SourceLocation &srcloc) : m_parent(parent), m_subject(subject), m_srcloc(srcloc) {}
void init() const;
void next() const;
QPointer<Expect> m_parent;
QPointer<QObject> m_subject;
SourceLocation m_srcloc;
QString m_waitingFor;
QVector<std::function<void()>> m_sends;
QVector<std::function<void()>> m_expects;
mutable QVector<std::function<void()>>::const_iterator m_nextExpect;
mutable ConnectGuard m_guard;
mutable std::function<void(const ExpectUnit *)> m_onDone;
void onDone(std::function<void(const ExpectUnit *)> callback) const { m_onDone = callback; }
// Helper traits class. Given T is a pointer-to-member-of-U, ClassOf<T>::type is U.
template <class T> struct ClassOf;
template <class U, class R> struct ClassOf<R U::*> { using type = U; };
// Given T is a pointer-to-member-of-U, subject<T>() returns Expect's subject casted to U*.
template <class T> typename ClassOf<T>::type *subject() const { return qobject_cast<typename ClassOf<T>::type *>(m_subject.data()); }
};
/*!
* Class for writing unit tests in which signals are sent to some object, and other signals are expected to be received in reply.
* Loosely modelled on the C library libexpect.
*
* Commonly used in combination with the macros EXPECT_UNIT or EXPECT_WAIT.
*/
class Expect : public QObject
{
Q_OBJECT
public:
/*!
* Constructor.
* \param testSubject The object to be tested.
*/
explicit Expect(QObject *testSubject) : m_subject(testSubject), m_failed(false) {}
/*!
* Add a single expectation that will remain for the lifetime of the object.
* \param signal A pointer-to-member-function of the subject class.
* \param slot A callable object which will be connected to the signal, e.g. to validate the signal arguments.
*/
template <class F1, class F2> void always(F1 signal, F2 slot)
{
m_guard += QObject::connect(m_subject, signal, slot);
}
/*!
* Returns a new unit of expectation for the subject. Commonly called via the EXPECT_UNIT macro.
* \param srcloc Represents the caller's location in the source code, for error reporting.
*/
ExpectUnit unit(const SourceLocation &srcloc = SOURCE_LOCATION) { return ExpectUnit(this, m_subject, srcloc); }
/*!
* Allows two or more units of expectation to be waited on simultaneously. Commonly valled via the EXPECT_WAIT macro.
* \param srcloc Represents the caller's location in the source code, for error reporting.
* \param timeout Time to wait in seconds. Qt event queue is processed when waiting.
* \param u1
*/
void wait(const SourceLocation &srcloc, int timeout, const ExpectUnit &u1) { m_units.insert(&u1); wait(srcloc, timeout); }
/*!
* \copydoc wait(const SourceLocation&,int,const ExpectUnit&)
* \param u2
*/
void wait(const SourceLocation &srcloc, int timeout, const ExpectUnit &u1, const ExpectUnit &u2) { m_units.insert(&u1); m_units.insert(&u2); wait(srcloc, timeout); }
/*!
* \copydoc wait(const SourceLocation&,int,const ExpectUnit&,const ExpectUnit&)
* \param u3
*/
void wait(const SourceLocation &srcloc, int timeout, const ExpectUnit &u1, const ExpectUnit &u2, const ExpectUnit &u3) { m_units.insert(&u1); m_units.insert(&u2); m_units.insert(&u3); wait(srcloc, timeout); }
/*!
* \copydoc wait(const SourceLocation&,int,const ExpectUnit&,const ExpectUnit&,const ExpectUnit&)
* \param u4
*/
void wait(const SourceLocation &srcloc, int timeout, const ExpectUnit &u1, const ExpectUnit &u2, const ExpectUnit &u3, const ExpectUnit &u4) { m_units.insert(&u1); m_units.insert(&u2); m_units.insert(&u3); m_units.insert(&u4); wait(srcloc, timeout); }
/*!
* \copydoc wait(const SourceLocation&,int,const ExpectUnit&,const ExpectUnit&,const ExpectUnit&,const ExpectUnit&)
* \param u5
*/
void wait(const SourceLocation &srcloc, int timeout, const ExpectUnit &u1, const ExpectUnit &u2, const ExpectUnit &u3, const ExpectUnit &u4, const ExpectUnit &u5) { m_units.insert(&u1); m_units.insert(&u2); m_units.insert(&u3); m_units.insert(&u4); m_units.insert(&u5); wait(srcloc, timeout); }
private:
friend class ExpectUnit;
void wait(const SourceLocation &srcloc, int timeout);
void reportTimeout(const SourceLocation &srcloc, const QSet<const ExpectUnit *> &units);
QPointer<QObject> m_subject;
QSet<const ExpectUnit *> m_units;
ConnectGuard m_guard;
bool m_failed;
};
/*!
* Wrapper for Expect::unit() which fills in the source location parameter.
* Returns a new unit of expectation for the subject.
* \param EXP Instance of Expect on which to call unit().
*/
#define EXPECT_UNIT(EXP) ((EXP).unit(SOURCE_LOCATION))
/*!
* Wrapper for Expect::wait() which fills in the source location parameter.
* Allows two or more units of expectation to be waited on simultaneously.
* \param EXP Instance of Expect on which to call wait().
* \param TIME Time to wait in seconds. Qt event queue is processed when waiting.
* \param U1, U2
*/
#define EXPECT_WAIT_2(EXP, TIME, U1, U2) ((EXP).wait(SOURCE_LOCATION, (TIME), (U1), (U2)))
/*!
* \copydoc EXPECT_WAIT_2(EXP,TIME,U1,U2)
* \param U3
*/
#define EXPECT_WAIT_3(EXP, TIME, U1, U2, U3) ((EXP).wait(SOURCE_LOCATION, (TIME), (U1), (U2), (U3)))
/*!
* \copydoc EXPECT_WAIT_3(EXP,TIME,U1,U2,U3)
* \param U4
*/
#define EXPECT_WAIT_4(EXP, TIME, U1, U2, U3, U4) ((EXP).wait(SOURCE_LOCATION, (TIME), (U1), (U2), (U3), (U4)))
/*!
* \copydoc EXPECT_WAIT_4(EXP,TIME,U1,U2,U3,U4)
* \param U5
*/
#define EXPECT_WAIT_5(EXP, TIME, U1, U2, U3, U4, U5) ((EXP).wait(SOURCE_LOCATION, (TIME), (U1), (U2), (U3), (U4), (U5)))
} // ns
//! \endcond
#endif // guard

View File

@@ -0,0 +1,170 @@
/* Copyright (C) 2015
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
//! \cond PRIVATE_TESTS
//! \file
//! \ingroup testblackcore
#include "expect.h"
#include "blackcore/application.h"
#include "blackcore/network.h"
#include "blackcore/vatsim/networkvatlib.h"
#include "blackmisc/aviation/aircrafticaocode.h"
#include "blackmisc/aviation/airlineicaocode.h"
#include "blackmisc/aviation/callsign.h"
#include "blackmisc/network/clientprovider.h"
#include "blackmisc/network/networkutils.h"
#include "blackmisc/network/server.h"
#include "blackmisc/network/url.h"
#include "blackmisc/network/user.h"
#include "blackmisc/pq/time.h"
#include "blackmisc/simulation/simulatedaircraft.h"
#include "blackmisc/simulation/ownaircraftproviderdummy.h"
#include "blackmisc/stringutils.h"
#include "test.h"
#include <QString>
#include <QTest>
#include <QThread>
#include <QtDebug>
using namespace BlackCore;
using namespace BlackCore::Vatsim;
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Simulation;
using namespace BlackMisc::Network;
using namespace BlackMisc::Geo;
using namespace BlackMisc::PhysicalQuantities;
namespace BlackCoreTest
{
//! INetwork implementation classes tests
class CTestNetwork : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
//! Test the vatlib
void networkVatlibTest() { networkTest(m_networkVatlib); }
void cleanupTestCase();
private:
//! Common part used by all tests.
void networkTest(BlackCore::INetwork *net);
CNetworkVatlib *m_networkVatlib = nullptr; //!< vatlib instance
//! Test if server is available
static bool pingServer(const BlackMisc::Network::CServer &server);
};
void CTestNetwork::initTestCase()
{
m_networkVatlib = new CNetworkVatlib(CClientProviderDummy::instance(), COwnAircraftProviderDummy::instance());
}
void CTestNetwork::networkTest(BlackCore::INetwork *net)
{
const CServer fsdServer = CServer::swiftFsdTestServer(true);
if (!this->pingServer(fsdServer)) { QSKIP("Server not reachable."); }
QString string = net->connectionStatusToString(INetwork::Connected);
QVERIFY(string == "Connected");
Expect e(net);
CSimulatedAircraft aircraft;
aircraft.setIcaoCodes(CAircraftIcaoCode("C172", "L1P"), CAirlineIcaoCode("YYY"));
EXPECT_UNIT(e)
.send(&INetwork::presetServer, fsdServer)
.send(&INetwork::presetCallsign, "SWIFT")
.send(&INetwork::presetIcaoCodes, aircraft)
.send(&INetwork::initiateConnection)
.expect(&INetwork::connectionStatusChanged, [](INetwork::ConnectionStatus, INetwork::ConnectionStatus newStatus)
{
QVERIFY(newStatus == INetwork::Connecting);
qDebug() << "CONNECTING";
})
.expect(&INetwork::connectionStatusChanged, [](INetwork::ConnectionStatus, INetwork::ConnectionStatus newStatus)
{
// we skip the test at the beginning if the server cannot be reached
// otherwise the whole build on Jenkins may fail
QVERIFY(newStatus == INetwork::Connected);
qDebug() << "CONNECTED";
})
.wait(10);
EXPECT_UNIT(e)
.send(&INetwork::sendPing, "server")
.expect(&INetwork::pongReceived, [](const CCallsign & callsign, const PhysicalQuantities::CTime & elapsedTime)
{
qDebug() << "PONG" << callsign << elapsedTime;
})
.wait(10);
EXPECT_UNIT(e)
.send(&INetwork::terminateConnection)
.expect(&INetwork::connectionStatusChanged, [](INetwork::ConnectionStatus, INetwork::ConnectionStatus newStatus)
{
QVERIFY(newStatus == INetwork::Disconnecting);
qDebug() << "DISCONNECTING";
})
.expect(&INetwork::connectionStatusChanged, [](INetwork::ConnectionStatus, INetwork::ConnectionStatus newStatus)
{
QVERIFY(newStatus == INetwork::Disconnected);
qDebug() << "DISCONNECTED";
})
.wait(10);
QThread::msleep(250); // make sure the last debug messages are written
}
bool CTestNetwork::pingServer(const CServer &server)
{
QString m;
const CUrl url(server.getAddress(), server.getPort());
if (!CNetworkUtils::canConnect(url, m, 2500))
{
qWarning() << "Skipping unit test as" << url.getFullUrl() << "cannot be connected";
return false;
}
return true;
}
void CTestNetwork::cleanupTestCase()
{
delete m_networkVatlib;
}
}
//! main
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
BLACKTEST_INIT(BlackCoreTest::CTestNetwork)
CApplication a(CApplicationInfo::UnitTest);
a.addVatlibOptions();
const bool setup = a.parseAndSynchronizeSetup();
if (!setup) { qWarning() << "No setup loaded"; }
int r = EXIT_FAILURE;
if (a.start())
{
r = QTest::qExec(&to, args);
}
a.gracefulShutdown();
return r;
}
#include "testnetwork.moc"
//! \endcond

View File

@@ -0,0 +1,33 @@
load(common_pre)
QT += core dbus network testlib
TARGET = testnetwork
CONFIG -= app_bundle
CONFIG += blackconfig
CONFIG += blackmisc
CONFIG += blackcore
CONFIG += testcase
CONFIG += no_testcase_installs
TEMPLATE = app
DEPENDPATH += \
. \
$$SourceRoot/src \
$$SourceRoot/tests \
INCLUDEPATH += \
$$SourceRoot/src \
$$SourceRoot/tests \
HEADERS += \
expect.h
SOURCES += \
testnetwork.cpp \
expect.cpp
DESTDIR = $$DestRoot/bin
load(common_post)

View File

@@ -0,0 +1,3 @@
TEMPLATE = subdirs
SUBDIRS += \
testnetwork \