1 /*
2     SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "racetest.h"
8 
9 #include <KProcess>
10 #include <QDebug>
11 
12 #include <Akonadi/AgentInstance>
13 #include <Akonadi/AgentInstanceCreateJob>
14 #include <Akonadi/AgentManager>
15 #include <Akonadi/AgentType>
16 #include <akonadi/qtest_akonadi.h>
17 #include <control.h>
18 //#include <localfolders.h>
19 
20 #define TIMEOUT_SECONDS 20
21 #define MAXCOUNT 10
22 // NOTE: REQUESTER_EXE is defined by cmake.
23 
24 Q_DECLARE_METATYPE(QProcess::ProcessError)
25 Q_DECLARE_METATYPE(QProcess::ExitStatus)
26 
27 using namespace Akonadi;
28 
initTestCase()29 void RaceTest::initTestCase()
30 {
31     QVERIFY(Control::start());
32     QTest::qWait(1000); // give the MDA time to start so that we can kill it in peace
33     qRegisterMetaType<QProcess::ProcessError>();
34     qRegisterMetaType<QProcess::ExitStatus>();
35 }
36 
testMultipleProcesses_data()37 void RaceTest::testMultipleProcesses_data()
38 {
39     QTest::addColumn<int>("count"); // how many processes to create
40     QTest::addColumn<int>("delay"); // number of ms to wait before starting next process
41 
42     QTest::newRow("1-nodelay") << 1 << 0;
43     QTest::newRow("2-nodelay") << 2 << 0;
44     QTest::newRow("5-nodelay") << 5 << 0;
45     QTest::newRow("10-nodelay") << 10 << 0;
46     QTest::newRow("2-shortdelay") << 2 << 100;
47     QTest::newRow("5-shortdelay") << 5 << 100;
48     QTest::newRow("10-shortdelay") << 10 << 100;
49     QTest::newRow("2-longdelay") << 2 << 1000;
50     QTest::newRow("5-longdelay") << 5 << 1000;
51     QTest::newRow("5-verylongdelay") << 5 << 4000;
52     Q_ASSERT(10 <= MAXCOUNT);
53 }
54 
testMultipleProcesses()55 void RaceTest::testMultipleProcesses()
56 {
57     QFETCH(int, count);
58     QFETCH(int, delay);
59 
60     killZombies();
61 
62     // Remove all maildir instances (at most 1 really) and MDAs (which use LocalFolders).
63     // (This is to ensure that one of *our* instances is the main instance.)
64     AgentType::List types;
65     types.append(AgentManager::self()->type(QLatin1String("akonadi_maildir_resource")));
66     types.append(AgentManager::self()->type(QLatin1String("akonadi_maildispatcher_agent")));
67     AgentInstance::List instances = AgentManager::self()->instances();
68     for (const AgentInstance &instance : std::as_const(instances)) {
69         if (types.contains(instance.type())) {
70             qDebug() << "Removing instance of type" << instance.type().identifier();
71             AgentManager::self()->removeInstance(instance);
72             QSignalSpy removedSpy(AgentManager::self(), SIGNAL(instanceRemoved(Akonadi::AgentInstance)));
73             QVERIFY(removedSpy.wait());
74         }
75     }
76     instances = AgentManager::self()->instances();
77     for (const AgentInstance &instance : std::as_const(instances)) {
78         QVERIFY(!types.contains(instance.type()));
79     }
80 
81     QSignalSpy *errorSpy[MAXCOUNT];
82     QSignalSpy *finishedSpy[MAXCOUNT];
83     for (int i = 0; i < count; i++) {
84         qDebug() << "Starting process" << i + 1 << "of" << count;
85         KProcess *proc = new KProcess;
86         procs.append(proc);
87         proc->setProgram(QStringLiteral(REQUESTER_EXE));
88         errorSpy[i] = new QSignalSpy(proc, SIGNAL(error(QProcess::ProcessError)));
89         finishedSpy[i] = new QSignalSpy(proc, SIGNAL(finished(int, QProcess::ExitStatus)));
90         proc->start();
91         QTest::qWait(delay);
92     }
93     qDebug() << "Launched" << count << "processes.";
94 
95     int seconds = 0;
96     int error, finished;
97     while (true) {
98         seconds++;
99         QTest::qWait(1000);
100 
101         error = 0;
102         finished = 0;
103         for (int i = 0; i < count; i++) {
104             if (errorSpy[i]->count() > 0) {
105                 error++;
106             }
107             if (finishedSpy[i]->count() > 0) {
108                 finished++;
109             }
110         }
111         qDebug() << seconds << "seconds elapsed." << error << "processes error'd," << finished << "processes finished.";
112 
113         if (error + finished >= count) {
114             break;
115         }
116 
117 #if 0
118         if (seconds >= TIMEOUT_SECONDS) {
119             qDebug() << "Timeout, gdb master!";
120             QTest::qWait(1000 * 1000);
121         }
122 #endif
123         QVERIFY2(seconds < TIMEOUT_SECONDS, "Timeout");
124     }
125 
126     QCOMPARE(error, 0);
127     QCOMPARE(finished, count);
128     for (int i = 0; i < count; i++) {
129         qDebug() << "Checking exit status of process" << i + 1 << "of" << count;
130         QCOMPARE(finishedSpy[i]->count(), 1);
131         QList<QVariant> args = finishedSpy[i]->takeFirst();
132         if (args[0].toInt() != 2) {
133             qDebug() << "Exit status" << args[0].toInt() << ", expected 2. Timeout, gdb master!";
134             QTest::qWait(1000 * 1000);
135         }
136         QCOMPARE(args[0].toInt(), 2);
137     }
138 
139     while (!procs.isEmpty()) {
140         KProcess *proc = procs.takeFirst();
141         QCOMPARE(proc->exitStatus(), QProcess::NormalExit);
142         QCOMPARE(proc->exitCode(), 2);
143         delete proc;
144     }
145     QVERIFY(procs.isEmpty());
146 }
147 
killZombies()148 void RaceTest::killZombies()
149 {
150     while (!procs.isEmpty()) {
151         // These processes probably hung, and will never recover, so we need to kill them.
152         // (This happens if the last test failed.)
153         qDebug() << "Killing zombies from the past.";
154         KProcess *proc = procs.takeFirst();
155         proc->kill();
156         proc->deleteLater();
157     }
158 }
159 
160 QTEST_AKONADIMAIN(RaceTest)
161