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