File indexing completed on 2024-05-12 05:26:26

0001 #include <QTest>
0002 
0003 #include "dummyresource/resourcefactory.h"
0004 #include "resourcecontrol.h"
0005 #include "store.h"
0006 #include "test.h"
0007 #include "resourceconfig.h"
0008 #include "resourceaccess.h"
0009 #include "commands.h"
0010 
0011 /**
0012  * Test starting and stopping of resources.
0013  */
0014 class ResourceControlTest : public QObject
0015 {
0016     Q_OBJECT
0017 
0018     KAsync::Job<bool> socketIsAvailable(const QByteArray &identifier)
0019     {
0020         return Sink::ResourceAccess::connectToServer(identifier)
0021             .then<void, QSharedPointer<QLocalSocket>>(
0022                 [&](const KAsync::Error &error, QSharedPointer<QLocalSocket> socket) {
0023                     if (error) {
0024                         return KAsync::value(false);
0025                     }
0026                     socket->close();
0027                     return KAsync::value(true);
0028                 });
0029 
0030     }
0031 
0032     bool blockingSocketIsAvailable(const QByteArray &identifier)
0033     {
0034         auto job = socketIsAvailable(identifier);
0035         auto future = job.exec();
0036         future.waitForFinished();
0037         return future.value();
0038     }
0039 
0040 private slots:
0041 
0042     void initTestCase()
0043     {
0044         Sink::Test::initTest();
0045         auto factory = Sink::ResourceFactory::load("sink.dummy");
0046         QVERIFY(factory);
0047         ::DummyResource::removeFromDisk("sink.dummy.instance1");
0048         ResourceConfig::addResource("sink.dummy.instance1", "sink.dummy");
0049         ::DummyResource::removeFromDisk("sink.dummy.instance2");
0050         ResourceConfig::addResource("sink.dummy.instance2", "sink.dummy");
0051     }
0052 
0053     void testResourceStart()
0054     {
0055         VERIFYEXEC(Sink::ResourceControl::start("sink.dummy.instance1"));
0056         QVERIFY(blockingSocketIsAvailable("sink.dummy.instance1"));
0057     }
0058 
0059     void testResourceShutdown()
0060     {
0061         QVERIFY(!blockingSocketIsAvailable("sink.dummy.instance2"));
0062         VERIFYEXEC(Sink::ResourceControl::start("sink.dummy.instance2"));
0063         QVERIFY(blockingSocketIsAvailable("sink.dummy.instance2"));
0064         VERIFYEXEC(Sink::ResourceControl::shutdown("sink.dummy.instance2"));
0065         QVERIFY(!blockingSocketIsAvailable("sink.dummy.instance2"));
0066     }
0067 
0068     //This will produce a race where the synchronize command starts the resource,
0069     //the shutdown command doesn't shutdown because it doesn't realize that the resource is up,
0070     //and the resource ends up getting started, but doing nothing.
0071     void testResourceShutdownAfterStartByCommand()
0072     {
0073         QVERIFY(!blockingSocketIsAvailable("sink.dummy.instance2"));
0074         auto future = Sink::Store::synchronize(Sink::SyncScope{}.resourceFilter("sink.dummy.instance2")).exec();
0075 
0076         VERIFYEXEC(Sink::ResourceControl::shutdown("sink.dummy.instance2"));
0077 
0078         QVERIFY(!blockingSocketIsAvailable("sink.dummy.instance2"));
0079     }
0080 
0081     /**
0082      * An existing live-query should not restart the resource due to revisionReplayedCommands.
0083      * This was introduced for tests, in regular use the resources are running during the whole query anyways,
0084      * because a live query will start the resource via an explicit call to open().
0085      */
0086     void testRevisionReplayedAfterShutdown()
0087     {
0088         //Prepare
0089         const QByteArray identifier{"sink.dummy.instance2"};
0090         QVERIFY(!blockingSocketIsAvailable(identifier));
0091         VERIFYEXEC(Sink::ResourceControl::start(identifier));
0092         QVERIFY(blockingSocketIsAvailable(identifier));
0093         auto resourceAccess = Sink::ResourceAccessFactory::instance().getAccess(identifier, ResourceConfig::getResourceType(identifier));
0094 
0095         //Shutdown and immediately send a revision replayed command
0096         VERIFYEXEC(Sink::ResourceControl::shutdown(identifier));
0097         VERIFYEXEC_FAIL(resourceAccess->sendRevisionReplayedCommand(1));
0098 
0099         //This should not start the resource again
0100         QVERIFY(!blockingSocketIsAvailable("sink.dummy.instance2"));
0101     }
0102 
0103     void testAbortCommandsOnShutdown()
0104     {
0105         const QByteArray identifier{"sink.dummy.instance1"};
0106         VERIFYEXEC(Sink::ResourceControl::shutdown(identifier));
0107         auto resourceAccess = Sink::ResourceAccessFactory::instance().getAccess(identifier, ResourceConfig::getResourceType(identifier));
0108         resourceAccess->shutdown().exec();
0109         //This operation should be aborted by the shutdown operation
0110         VERIFYEXEC_FAIL(Sink::ResourceControl::start(identifier));
0111     }
0112 
0113     void testResourceShutdownRestartLoop()
0114     {
0115         const QByteArray identifier{"sink.dummy.instance1"};
0116         VERIFYEXEC(Sink::ResourceControl::shutdown(identifier));
0117         QVERIFY(!blockingSocketIsAvailable(identifier));
0118         for (int i = 0; i < 10; i++) {
0119             Sink::ResourceControl::start(identifier).exec().waitForFinished();
0120             Sink::ResourceControl::shutdown(identifier).exec().waitForFinished();
0121         }
0122         QVERIFY(!blockingSocketIsAvailable(identifier));
0123     }
0124 
0125     /**
0126      * This test used to trigger a SIGPIPE, before we started to abort the socket on shutdown.
0127      */
0128     void testResourceShutdownRestartWithCommandLoop()
0129     {
0130         const QByteArray identifier{"sink.dummy.instance1"};
0131         VERIFYEXEC(Sink::ResourceControl::shutdown(identifier));
0132         QVERIFY(!blockingSocketIsAvailable(identifier));
0133         for (int i = 0; i < 10; i++) {
0134             auto resourceAccess = Sink::ResourceAccessFactory::instance().getAccess(identifier, ResourceConfig::getResourceType(identifier));
0135             resourceAccess->sendCommand(Sink::Commands::PingCommand).exec();
0136             resourceAccess->shutdown().exec().waitForFinished();
0137             Sink::ResourceControl::start(identifier).exec().waitForFinished();
0138         }
0139 
0140         VERIFYEXEC(Sink::ResourceControl::shutdown(identifier));
0141         QVERIFY(!blockingSocketIsAvailable(identifier));
0142     }
0143 
0144     /**
0145      * This seems to somehow corrupt the stack and crashes with
0146      *  malloc(): unaligned tcache chunk detected
0147      */
0148     void testResourceShutdownCrash()
0149     {
0150         QSKIP("Results in a crash");
0151         const QByteArray identifier{"sink.dummy.instance1"};
0152         VERIFYEXEC(Sink::ResourceControl::shutdown(identifier));
0153         QVERIFY(!blockingSocketIsAvailable(identifier));
0154         {
0155             auto resourceAccess = Sink::ResourceAccessFactory::instance().getAccess(identifier, ResourceConfig::getResourceType(identifier));
0156             QTest::qWait(500);
0157             resourceAccess->shutdown().exec().waitForFinished();
0158         }
0159         Sink::ResourceControl::start(identifier).exec().waitForFinished();
0160 
0161         VERIFYEXEC(Sink::ResourceControl::shutdown(identifier));
0162         QVERIFY(!blockingSocketIsAvailable(identifier));
0163     }
0164 
0165 };
0166 
0167 QTEST_MAIN(ResourceControlTest)
0168 #include "resourcecontroltest.moc"