1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QDebug> |
30 | #include <QFile> |
31 | #if QT_CONFIG(process) |
32 | # include <QProcess> |
33 | #endif |
34 | #include <QSharedMemory> |
35 | #include <QTest> |
36 | #include <QThread> |
37 | #include <QElapsedTimer> |
38 | #include <QScopeGuard> |
39 | |
40 | #define EXISTING_SHARE "existing" |
41 | #define EXISTING_SIZE 1024 |
42 | |
43 | Q_DECLARE_METATYPE(QSharedMemory::SharedMemoryError) |
44 | Q_DECLARE_METATYPE(QSharedMemory::AccessMode) |
45 | |
46 | class tst_QSharedMemory : public QObject |
47 | { |
48 | Q_OBJECT |
49 | |
50 | public: |
51 | tst_QSharedMemory(); |
52 | virtual ~tst_QSharedMemory(); |
53 | |
54 | public Q_SLOTS: |
55 | void init(); |
56 | void cleanup(); |
57 | |
58 | |
59 | private slots: |
60 | // basics |
61 | void constructor(); |
62 | void key_data(); |
63 | void key(); |
64 | void create_data(); |
65 | void create(); |
66 | void attach_data(); |
67 | void attach(); |
68 | void lock(); |
69 | |
70 | // custom edge cases |
71 | #ifndef Q_OS_HPUX |
72 | void removeWhileAttached(); |
73 | #endif |
74 | void emptyMemory(); |
75 | #if !defined(Q_OS_WIN) |
76 | void readOnly(); |
77 | #endif |
78 | |
79 | // basics all together |
80 | #ifndef Q_OS_HPUX |
81 | void simpleProducerConsumer_data(); |
82 | void simpleProducerConsumer(); |
83 | void simpleDoubleProducerConsumer(); |
84 | #endif |
85 | |
86 | // with threads |
87 | void simpleThreadedProducerConsumer_data(); |
88 | void simpleThreadedProducerConsumer(); |
89 | |
90 | // with processes |
91 | void simpleProcessProducerConsumer_data(); |
92 | void simpleProcessProducerConsumer(); |
93 | |
94 | // extreme cases |
95 | void useTooMuchMemory(); |
96 | #if !defined(Q_OS_HPUX) |
97 | void attachTooMuch(); |
98 | #endif |
99 | |
100 | // unique keys |
101 | void uniqueKey_data(); |
102 | void uniqueKey(); |
103 | |
104 | protected: |
105 | int remove(const QString &key); |
106 | |
107 | QString rememberKey(const QString &key) |
108 | { |
109 | if (key == EXISTING_SHARE) |
110 | return key; |
111 | if (!keys.contains(str: key)) { |
112 | keys.append(t: key); |
113 | remove(key); |
114 | } |
115 | return key; |
116 | } |
117 | |
118 | QStringList keys; |
119 | QList<QSharedMemory*> jail; |
120 | QSharedMemory *existingSharedMemory; |
121 | |
122 | private: |
123 | const QString m_helperBinary; |
124 | }; |
125 | |
126 | tst_QSharedMemory::tst_QSharedMemory() |
127 | : existingSharedMemory(0) |
128 | , m_helperBinary("producerconsumer_helper" ) |
129 | { |
130 | } |
131 | |
132 | tst_QSharedMemory::~tst_QSharedMemory() |
133 | { |
134 | } |
135 | |
136 | void tst_QSharedMemory::init() |
137 | { |
138 | existingSharedMemory = new QSharedMemory(EXISTING_SHARE); |
139 | if (!existingSharedMemory->create(EXISTING_SIZE)) { |
140 | QCOMPARE(existingSharedMemory->error(), QSharedMemory::AlreadyExists); |
141 | } |
142 | } |
143 | |
144 | void tst_QSharedMemory::cleanup() |
145 | { |
146 | delete existingSharedMemory; |
147 | qDeleteAll(begin: jail.begin(), end: jail.end()); |
148 | jail.clear(); |
149 | |
150 | keys.append(EXISTING_SHARE); |
151 | for (int i = 0; i < keys.count(); ++i) { |
152 | QSharedMemory sm(keys.at(i)); |
153 | if (!sm.create(size: 1024)) { |
154 | //if (sm.error() != QSharedMemory::KeyError) |
155 | // qWarning() << "test cleanup: remove failed:" << keys.at(i) << sm.error() << sm.errorString(); |
156 | sm.attach(); |
157 | sm.detach(); |
158 | remove(key: keys.at(i)); |
159 | } |
160 | } |
161 | } |
162 | |
163 | #ifndef Q_OS_WIN |
164 | #include <private/qsharedmemory_p.h> |
165 | #include <sys/types.h> |
166 | #ifndef QT_POSIX_IPC |
167 | #include <sys/ipc.h> |
168 | #include <sys/shm.h> |
169 | #else |
170 | #include <sys/mman.h> |
171 | #endif // QT_POSIX_IPC |
172 | #include <errno.h> |
173 | #endif |
174 | |
175 | int tst_QSharedMemory::remove(const QString &key) |
176 | { |
177 | #ifdef Q_OS_WIN |
178 | Q_UNUSED(key); |
179 | return 0; |
180 | #else |
181 | // On unix the shared memory might exists from a previously failed test |
182 | // or segfault, remove it it does |
183 | if (key.isEmpty()) |
184 | return -1; |
185 | |
186 | // ftok requires that an actual file exists somewhere |
187 | QString fileName = QSharedMemoryPrivate::makePlatformSafeKey(key); |
188 | if (!QFile::exists(fileName)) { |
189 | //qDebug() << "exits failed"; |
190 | return -2; |
191 | } |
192 | |
193 | #ifndef QT_POSIX_IPC |
194 | int unix_key = ftok(pathname: fileName.toLatin1().constData(), proj_id: 'Q'); |
195 | if (-1 == unix_key) { |
196 | qDebug() << "ftok failed" ; |
197 | return -3; |
198 | } |
199 | |
200 | int id = shmget(key: unix_key, size: 0, shmflg: 0600); |
201 | if (-1 == id) { |
202 | qDebug() << "shmget failed" << strerror(errno); |
203 | return -4; |
204 | } |
205 | |
206 | struct shmid_ds shmid_ds; |
207 | if (-1 == shmctl(shmid: id, IPC_RMID, buf: &shmid_ds)) { |
208 | qDebug() << "shmctl failed" ; |
209 | return -5; |
210 | } |
211 | #else |
212 | if (shm_unlink(QFile::encodeName(fileName).constData()) == -1) { |
213 | qDebug() << "shm_unlink failed" ; |
214 | return -5; |
215 | } |
216 | #endif // QT_POSIX_IPC |
217 | |
218 | return QFile::remove(fileName); |
219 | #endif // Q_OS_WIN |
220 | } |
221 | |
222 | /*! |
223 | Tests the default values |
224 | */ |
225 | void tst_QSharedMemory::constructor() |
226 | { |
227 | QSharedMemory sm; |
228 | QCOMPARE(sm.key(), QString()); |
229 | QVERIFY(!sm.isAttached()); |
230 | QVERIFY(!sm.data()); |
231 | QCOMPARE(sm.size(), 0); |
232 | QCOMPARE(sm.error(), QSharedMemory::NoError); |
233 | QCOMPARE(sm.errorString(), QString()); |
234 | } |
235 | |
236 | void tst_QSharedMemory::key_data() |
237 | { |
238 | QTest::addColumn<QString>(name: "constructorKey" ); |
239 | QTest::addColumn<QString>(name: "setKey" ); |
240 | QTest::addColumn<QString>(name: "setNativeKey" ); |
241 | |
242 | QTest::newRow(dataTag: "null, null, null" ) << QString() << QString() << QString(); |
243 | QTest::newRow(dataTag: "one, null, null" ) << QString("one" ) << QString() << QString(); |
244 | QTest::newRow(dataTag: "null, one, null" ) << QString() << QString("one" ) << QString(); |
245 | QTest::newRow(dataTag: "null, null, one" ) << QString() << QString() << QString("one" ); |
246 | QTest::newRow(dataTag: "one, two, null" ) << QString("one" ) << QString("two" ) << QString(); |
247 | QTest::newRow(dataTag: "one, null, two" ) << QString("one" ) << QString() << QString("two" ); |
248 | QTest::newRow(dataTag: "null, one, two" ) << QString() << QString("one" ) << QString("two" ); |
249 | QTest::newRow(dataTag: "one, two, three" ) << QString("one" ) << QString("two" ) << QString("three" ); |
250 | QTest::newRow(dataTag: "invalid" ) << QString("o/e" ) << QString("t/o" ) << QString("|x" ); |
251 | } |
252 | |
253 | /*! |
254 | Basic key testing |
255 | */ |
256 | void tst_QSharedMemory::key() |
257 | { |
258 | QFETCH(QString, constructorKey); |
259 | QFETCH(QString, setKey); |
260 | QFETCH(QString, setNativeKey); |
261 | |
262 | QSharedMemory sm(constructorKey); |
263 | QCOMPARE(sm.key(), constructorKey); |
264 | QCOMPARE(sm.nativeKey().isEmpty(), constructorKey.isEmpty()); |
265 | sm.setKey(setKey); |
266 | QCOMPARE(sm.key(), setKey); |
267 | QCOMPARE(sm.nativeKey().isEmpty(), setKey.isEmpty()); |
268 | sm.setNativeKey(setNativeKey); |
269 | QVERIFY(sm.key().isNull()); |
270 | QCOMPARE(sm.nativeKey(), setNativeKey); |
271 | QCOMPARE(sm.isAttached(), false); |
272 | |
273 | QCOMPARE(sm.error(), QSharedMemory::NoError); |
274 | QCOMPARE(sm.errorString(), QString()); |
275 | QVERIFY(!sm.data()); |
276 | QCOMPARE(sm.size(), 0); |
277 | |
278 | QCOMPARE(sm.detach(), false); |
279 | } |
280 | |
281 | void tst_QSharedMemory::create_data() |
282 | { |
283 | QTest::addColumn<QString>(name: "key" ); |
284 | QTest::addColumn<int>(name: "size" ); |
285 | QTest::addColumn<bool>(name: "canCreate" ); |
286 | QTest::addColumn<QSharedMemory::SharedMemoryError>(name: "error" ); |
287 | |
288 | QTest::newRow(dataTag: "null key" ) << QString() << 1024 |
289 | << false << QSharedMemory::KeyError; |
290 | QTest::newRow(dataTag: "-1 size" ) << QString("negsize" ) << -1 |
291 | << false << QSharedMemory::InvalidSize; |
292 | QTest::newRow(dataTag: "nor size" ) << QString("norsize" ) << 1024 |
293 | << true << QSharedMemory::NoError; |
294 | QTest::newRow(dataTag: "already exists" ) << QString(EXISTING_SHARE) << EXISTING_SIZE |
295 | << false << QSharedMemory::AlreadyExists; |
296 | } |
297 | |
298 | /*! |
299 | Basic create testing |
300 | */ |
301 | void tst_QSharedMemory::create() |
302 | { |
303 | QFETCH(QString, key); |
304 | QFETCH(int, size); |
305 | QFETCH(bool, canCreate); |
306 | QFETCH(QSharedMemory::SharedMemoryError, error); |
307 | |
308 | QSharedMemory sm(rememberKey(key)); |
309 | QCOMPARE(sm.create(size), canCreate); |
310 | if (sm.error() != error) |
311 | qDebug() << sm.errorString(); |
312 | QCOMPARE(sm.key(), key); |
313 | if (canCreate) { |
314 | QCOMPARE(sm.errorString(), QString()); |
315 | QVERIFY(sm.data() != 0); |
316 | QVERIFY(sm.size() != 0); |
317 | } else { |
318 | QVERIFY(!sm.data()); |
319 | QVERIFY(sm.errorString() != QString()); |
320 | } |
321 | } |
322 | |
323 | void tst_QSharedMemory::attach_data() |
324 | { |
325 | QTest::addColumn<QString>(name: "key" ); |
326 | QTest::addColumn<bool>(name: "exists" ); |
327 | QTest::addColumn<QSharedMemory::SharedMemoryError>(name: "error" ); |
328 | |
329 | QTest::newRow(dataTag: "null key" ) << QString() << false << QSharedMemory::KeyError; |
330 | QTest::newRow(dataTag: "doesn't exists" ) << QString("doesntexists" ) << false << QSharedMemory::NotFound; |
331 | |
332 | // HPUX doesn't allow for multiple attaches per process. |
333 | #ifndef Q_OS_HPUX |
334 | QTest::newRow(dataTag: "already exists" ) << QString(EXISTING_SHARE) << true << QSharedMemory::NoError; |
335 | #endif |
336 | } |
337 | |
338 | /*! |
339 | Basic attach/detach testing |
340 | */ |
341 | void tst_QSharedMemory::attach() |
342 | { |
343 | QFETCH(QString, key); |
344 | QFETCH(bool, exists); |
345 | QFETCH(QSharedMemory::SharedMemoryError, error); |
346 | |
347 | QSharedMemory sm(key); |
348 | QCOMPARE(sm.attach(), exists); |
349 | QCOMPARE(sm.isAttached(), exists); |
350 | QCOMPARE(sm.error(), error); |
351 | QCOMPARE(sm.key(), key); |
352 | if (exists) { |
353 | QVERIFY(sm.data() != 0); |
354 | QVERIFY(sm.size() != 0); |
355 | QCOMPARE(sm.errorString(), QString()); |
356 | QVERIFY(sm.detach()); |
357 | // Make sure detach doesn't screw up something and we can't re-attach. |
358 | QVERIFY(sm.attach()); |
359 | QVERIFY(sm.data() != 0); |
360 | QVERIFY(sm.size() != 0); |
361 | QVERIFY(sm.detach()); |
362 | QCOMPARE(sm.size(), 0); |
363 | QVERIFY(!sm.data()); |
364 | } else { |
365 | QVERIFY(!sm.data()); |
366 | QCOMPARE(sm.size(), 0); |
367 | QVERIFY(sm.errorString() != QString()); |
368 | QVERIFY(!sm.detach()); |
369 | } |
370 | } |
371 | |
372 | void tst_QSharedMemory::lock() |
373 | { |
374 | QSharedMemory shm; |
375 | QVERIFY(!shm.lock()); |
376 | QCOMPARE(shm.error(), QSharedMemory::LockError); |
377 | |
378 | shm.setKey(QLatin1String("qsharedmemory" )); |
379 | |
380 | QVERIFY(!shm.lock()); |
381 | QCOMPARE(shm.error(), QSharedMemory::LockError); |
382 | |
383 | QVERIFY(shm.create(100)); |
384 | QVERIFY(shm.lock()); |
385 | QTest::ignoreMessage(type: QtWarningMsg, message: "QSharedMemory::lock: already locked" ); |
386 | QVERIFY(shm.lock()); |
387 | // we didn't unlock(), so ignore the warning from auto-detach in destructor |
388 | QTest::ignoreMessage(type: QtWarningMsg, message: "QSharedMemory::lock: already locked" ); |
389 | } |
390 | |
391 | /*! |
392 | Other shared memory are allowed to be attached after we remove, |
393 | but new shared memory are not allowed to attach after a remove. |
394 | */ |
395 | // HPUX doesn't allow for multiple attaches per process. |
396 | #ifndef Q_OS_HPUX |
397 | void tst_QSharedMemory::removeWhileAttached() |
398 | { |
399 | rememberKey(key: "one" ); |
400 | |
401 | // attach 1 |
402 | QSharedMemory *smOne = new QSharedMemory(QLatin1String("one" )); |
403 | QVERIFY(smOne->create(1024)); |
404 | QVERIFY(smOne->isAttached()); |
405 | |
406 | // attach 2 |
407 | QSharedMemory *smTwo = new QSharedMemory(QLatin1String("one" )); |
408 | QVERIFY(smTwo->attach()); |
409 | QVERIFY(smTwo->isAttached()); |
410 | |
411 | // detach 1 and remove, remove one first to catch another error. |
412 | delete smOne; |
413 | delete smTwo; |
414 | |
415 | // three shouldn't be able to attach |
416 | QSharedMemory smThree(QLatin1String("one" )); |
417 | QVERIFY(!smThree.attach()); |
418 | QCOMPARE(smThree.error(), QSharedMemory::NotFound); |
419 | } |
420 | #endif |
421 | |
422 | /*! |
423 | The memory should be set to 0 after created. |
424 | */ |
425 | void tst_QSharedMemory::emptyMemory() |
426 | { |
427 | QSharedMemory sm(rememberKey(key: QLatin1String("voidland" ))); |
428 | int size = 1024; |
429 | QVERIFY(sm.create(size, QSharedMemory::ReadOnly)); |
430 | char *get = (char*)sm.data(); |
431 | char null = 0; |
432 | for (int i = 0; i < size; ++i) |
433 | QCOMPARE(get[i], null); |
434 | } |
435 | |
436 | /*! |
437 | Verify that attach with ReadOnly is actually read only |
438 | by writing to data and causing a segfault. |
439 | */ |
440 | // This test opens a crash dialog on Windows. |
441 | #if !defined(Q_OS_WIN) |
442 | void tst_QSharedMemory::readOnly() |
443 | { |
444 | #if !QT_CONFIG(process) |
445 | QSKIP("No qprocess support" , SkipAll); |
446 | #elif defined(Q_OS_MACOS) |
447 | QSKIP("QTBUG-59936: Times out on macOS" , SkipAll); |
448 | #elif defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer) |
449 | QSKIP("ASan prevents the crash this test is looking for." , SkipAll); |
450 | #else |
451 | rememberKey(key: "readonly_segfault" ); |
452 | |
453 | // Add the executable's directory to path so that we can find the test helper next to it |
454 | // in a cross-platform way. We must do this because the CWD is not pointing to this directory |
455 | // in debug-and-release builds. |
456 | QByteArray path = qgetenv(varName: "PATH" ); |
457 | qputenv(varName: "PATH" , |
458 | value: path + QDir::listSeparator().toLatin1() |
459 | + QCoreApplication::applicationDirPath().toLocal8Bit()); |
460 | auto restore = qScopeGuard(f: [&] { qputenv(varName: "PATH" , value: path); }); |
461 | |
462 | // ### on windows disable the popup somehow |
463 | QProcess p; |
464 | p.start(program: m_helperBinary, arguments: QStringList("readonly_segfault" )); |
465 | p.setProcessChannelMode(QProcess::ForwardedChannels); |
466 | p.waitForFinished(); |
467 | QCOMPARE(p.error(), QProcess::Crashed); |
468 | #endif |
469 | } |
470 | #endif |
471 | |
472 | /*! |
473 | Keep making shared memory until the kernel stops us. |
474 | */ |
475 | void tst_QSharedMemory::useTooMuchMemory() |
476 | { |
477 | #ifdef Q_OS_LINUX |
478 | bool success = true; |
479 | int count = 0; |
480 | while (success) { |
481 | QString key = QLatin1String("maxmemorytest_" ) + QString::number(count++); |
482 | QSharedMemory *sm = new QSharedMemory(rememberKey(key)); |
483 | QVERIFY(sm); |
484 | jail.append(t: sm); |
485 | int size = 32768 * 1024; |
486 | success = sm->create(size); |
487 | if (!success && sm->error() == QSharedMemory::AlreadyExists) { |
488 | // left over from a crash, clean it up |
489 | sm->attach(); |
490 | sm->detach(); |
491 | success = sm->create(size); |
492 | } |
493 | |
494 | if (!success) { |
495 | QVERIFY(!sm->isAttached()); |
496 | QCOMPARE(sm->key(), key); |
497 | QCOMPARE(sm->size(), 0); |
498 | QVERIFY(!sm->data()); |
499 | if (sm->error() != QSharedMemory::OutOfResources) |
500 | qDebug() << sm->error() << sm->errorString(); |
501 | // ### Linux won't return OutOfResources if there are not enough semaphores to use. |
502 | QVERIFY(sm->error() == QSharedMemory::OutOfResources |
503 | || sm->error() == QSharedMemory::LockError); |
504 | QVERIFY(sm->errorString() != QString()); |
505 | QVERIFY(!sm->attach()); |
506 | QVERIFY(!sm->detach()); |
507 | } else { |
508 | QVERIFY(sm->isAttached()); |
509 | } |
510 | } |
511 | #endif |
512 | } |
513 | |
514 | /*! |
515 | Create one shared memory (government) and see how many other shared memories (wars) we can |
516 | attach before the system runs out of resources. |
517 | */ |
518 | // HPUX doesn't allow for multiple attaches per process. |
519 | #if !defined(Q_OS_HPUX) |
520 | void tst_QSharedMemory::attachTooMuch() |
521 | { |
522 | QSKIP("disabled" ); |
523 | |
524 | QSharedMemory government(rememberKey(key: "government" )); |
525 | QVERIFY(government.create(1024)); |
526 | while (true) { |
527 | QSharedMemory *war = new QSharedMemory(government.key()); |
528 | QVERIFY(war); |
529 | jail.append(t: war); |
530 | if (!war->attach()) { |
531 | QVERIFY(!war->isAttached()); |
532 | QCOMPARE(war->key(), government.key()); |
533 | QCOMPARE(war->size(), 0); |
534 | QVERIFY(!war->data()); |
535 | QCOMPARE(war->error(), QSharedMemory::OutOfResources); |
536 | QVERIFY(war->errorString() != QString()); |
537 | QVERIFY(!war->detach()); |
538 | break; |
539 | } else { |
540 | QVERIFY(war->isAttached()); |
541 | } |
542 | } |
543 | } |
544 | #endif |
545 | |
546 | // HPUX doesn't allow for multiple attaches per process. |
547 | #ifndef Q_OS_HPUX |
548 | void tst_QSharedMemory::simpleProducerConsumer_data() |
549 | { |
550 | QTest::addColumn<QSharedMemory::AccessMode>(name: "mode" ); |
551 | |
552 | QTest::newRow(dataTag: "readonly" ) << QSharedMemory::ReadOnly; |
553 | QTest::newRow(dataTag: "readwrite" ) << QSharedMemory::ReadWrite; |
554 | } |
555 | |
556 | /*! |
557 | The basic consumer producer that rounds out the basic testing. |
558 | If this fails then any muli-threading/process might fail (but be |
559 | harder to debug) |
560 | |
561 | This doesn't require nor test any locking system. |
562 | */ |
563 | void tst_QSharedMemory::simpleProducerConsumer() |
564 | { |
565 | QFETCH(QSharedMemory::AccessMode, mode); |
566 | |
567 | rememberKey(key: QLatin1String("market" )); |
568 | QSharedMemory producer(QLatin1String("market" )); |
569 | QSharedMemory consumer(QLatin1String("market" )); |
570 | int size = 512; |
571 | QVERIFY(producer.create(size)); |
572 | QVERIFY(consumer.attach(mode)); |
573 | |
574 | char *put = (char*)producer.data(); |
575 | char *get = (char*)consumer.data(); |
576 | // On Windows CE you always have ReadWrite access. Thus |
577 | // ViewMapOfFile returns the same pointer |
578 | QVERIFY(put != get); |
579 | for (int i = 0; i < size; ++i) { |
580 | put[i] = 'Q'; |
581 | QCOMPARE(get[i], 'Q'); |
582 | } |
583 | QVERIFY(consumer.detach()); |
584 | } |
585 | #endif |
586 | |
587 | // HPUX doesn't allow for multiple attaches per process. |
588 | #ifndef Q_OS_HPUX |
589 | void tst_QSharedMemory::simpleDoubleProducerConsumer() |
590 | { |
591 | rememberKey(key: QLatin1String("market" )); |
592 | QSharedMemory producer(QLatin1String("market" )); |
593 | int size = 512; |
594 | QVERIFY(producer.create(size)); |
595 | QVERIFY(producer.detach()); |
596 | QVERIFY(producer.create(size)); |
597 | |
598 | { |
599 | QSharedMemory consumer(QLatin1String("market" )); |
600 | QVERIFY(consumer.attach()); |
601 | } |
602 | } |
603 | #endif |
604 | |
605 | class Consumer : public QThread |
606 | { |
607 | |
608 | public: |
609 | void run() |
610 | { |
611 | QSharedMemory consumer(QLatin1String("market" )); |
612 | while (!consumer.attach()) { |
613 | if (consumer.error() != QSharedMemory::NotFound) |
614 | qDebug() << "consumer: failed to connect" << consumer.error() << consumer.errorString(); |
615 | QVERIFY(consumer.error() == QSharedMemory::NotFound || consumer.error() == QSharedMemory::KeyError); |
616 | QTest::qWait(ms: 1); |
617 | } |
618 | |
619 | char *memory = (char*)consumer.data(); |
620 | |
621 | int i = 0; |
622 | while (true) { |
623 | if (!consumer.lock()) |
624 | break; |
625 | if (memory[0] == 'Q') |
626 | memory[0] = ++i; |
627 | if (memory[0] == 'E') { |
628 | memory[1]++; |
629 | QVERIFY(consumer.unlock()); |
630 | break; |
631 | } |
632 | QVERIFY(consumer.unlock()); |
633 | QTest::qWait(ms: 1); |
634 | } |
635 | |
636 | QVERIFY(consumer.detach()); |
637 | } |
638 | }; |
639 | |
640 | class Producer : public QThread |
641 | { |
642 | |
643 | public: |
644 | Producer() : producer(QLatin1String("market" )) |
645 | { |
646 | int size = 1024; |
647 | if (!producer.create(size)) { |
648 | // left over from a crash... |
649 | if (producer.error() == QSharedMemory::AlreadyExists) { |
650 | producer.attach(); |
651 | producer.detach(); |
652 | QVERIFY(producer.create(size)); |
653 | } |
654 | } |
655 | } |
656 | |
657 | void run() |
658 | { |
659 | |
660 | char *memory = (char*)producer.data(); |
661 | memory[1] = '0'; |
662 | QElapsedTimer timer; |
663 | timer.start(); |
664 | int i = 0; |
665 | while (i < 5 && timer.elapsed() < 5000) { |
666 | QVERIFY(producer.lock()); |
667 | if (memory[0] == 'Q') { |
668 | QVERIFY(producer.unlock()); |
669 | QTest::qWait(ms: 1); |
670 | continue; |
671 | } |
672 | ++i; |
673 | memory[0] = 'Q'; |
674 | QVERIFY(producer.unlock()); |
675 | QTest::qWait(ms: 1); |
676 | } |
677 | |
678 | // tell everyone to quit |
679 | QVERIFY(producer.lock()); |
680 | memory[0] = 'E'; |
681 | QVERIFY(producer.unlock()); |
682 | |
683 | } |
684 | |
685 | QSharedMemory producer; |
686 | private: |
687 | |
688 | }; |
689 | |
690 | void tst_QSharedMemory::simpleThreadedProducerConsumer_data() |
691 | { |
692 | QTest::addColumn<bool>(name: "producerIsThread" ); |
693 | QTest::addColumn<int>(name: "threads" ); |
694 | for (int i = 0; i < 5; ++i) { |
695 | QTest::newRow(dataTag: "1 consumer, producer is thread" ) << true << 1; |
696 | QTest::newRow(dataTag: "1 consumer, producer is this" ) << false << 1; |
697 | QTest::newRow(dataTag: "5 consumers, producer is thread" ) << true << 5; |
698 | QTest::newRow(dataTag: "5 consumers, producer is this" ) << false << 5; |
699 | } |
700 | } |
701 | |
702 | /*! |
703 | The basic producer/consumer, but this time using threads. |
704 | */ |
705 | void tst_QSharedMemory::simpleThreadedProducerConsumer() |
706 | { |
707 | QFETCH(bool, producerIsThread); |
708 | QFETCH(int, threads); |
709 | rememberKey(key: QLatin1String("market" )); |
710 | |
711 | #if defined Q_OS_HPUX && defined __ia64 |
712 | QSKIP("This test locks up on gravlaks.troll.no" ); |
713 | #endif |
714 | |
715 | Producer p; |
716 | QVERIFY(p.producer.isAttached()); |
717 | if (producerIsThread) |
718 | p.start(); |
719 | |
720 | QList<Consumer*> consumers; |
721 | for (int i = 0; i < threads; ++i) { |
722 | consumers.append(t: new Consumer()); |
723 | consumers.last()->start(); |
724 | } |
725 | |
726 | if (!producerIsThread) |
727 | p.run(); |
728 | |
729 | p.wait(time: 5000); |
730 | while (!consumers.isEmpty()) { |
731 | Consumer *c = consumers.first(); |
732 | QVERIFY(c->isFinished() || c->wait(5000)); |
733 | delete consumers.takeFirst(); |
734 | } |
735 | } |
736 | |
737 | void tst_QSharedMemory::simpleProcessProducerConsumer_data() |
738 | { |
739 | #if QT_CONFIG(process) |
740 | QTest::addColumn<int>(name: "processes" ); |
741 | int tries = 5; |
742 | for (int i = 0; i < tries; ++i) { |
743 | QTest::newRow(dataTag: "1 process" ) << 1; |
744 | QTest::newRow(dataTag: "5 processes" ) << 5; |
745 | } |
746 | #endif |
747 | } |
748 | |
749 | /*! |
750 | Create external processes that produce and consume. |
751 | */ |
752 | void tst_QSharedMemory::simpleProcessProducerConsumer() |
753 | { |
754 | #if !QT_CONFIG(process) |
755 | QSKIP("No qprocess support" , SkipAll); |
756 | #else |
757 | QFETCH(int, processes); |
758 | |
759 | QSKIP("This test is unstable: QTBUG-25655" ); |
760 | |
761 | rememberKey(key: "market" ); |
762 | |
763 | // Add the executable's directory to path so that we can find the test helper next to it |
764 | // in a cross-platform way. We must do this because the CWD is not pointing to this directory |
765 | // in debug-and-release builds. |
766 | QByteArray path = qgetenv(varName: "PATH" ); |
767 | qputenv(varName: "PATH" , |
768 | value: path + QDir::listSeparator().toLatin1() |
769 | + QCoreApplication::applicationDirPath().toLocal8Bit()); |
770 | auto restore = qScopeGuard(f: [&] { qputenv(varName: "PATH" , value: path); }); |
771 | |
772 | QProcess producer; |
773 | producer.start(program: m_helperBinary, arguments: QStringList("producer" )); |
774 | QVERIFY2(producer.waitForStarted(), "Could not start helper binary" ); |
775 | QVERIFY2(producer.waitForReadyRead(), "Helper process failed to create shared memory segment: " + |
776 | producer.readAllStandardError()); |
777 | |
778 | QList<QProcess*> consumers; |
779 | unsigned int failedProcesses = 0; |
780 | const QStringList consumerArguments = QStringList("consumer" ); |
781 | for (int i = 0; i < processes; ++i) { |
782 | QProcess *p = new QProcess; |
783 | p->setProcessChannelMode(QProcess::ForwardedChannels); |
784 | p->start(program: m_helperBinary, arguments: consumerArguments); |
785 | if (p->waitForStarted(msecs: 2000)) |
786 | consumers.append(t: p); |
787 | else |
788 | ++failedProcesses; |
789 | } |
790 | |
791 | bool consumerFailed = false; |
792 | |
793 | while (!consumers.isEmpty()) { |
794 | QVERIFY(consumers.first()->waitForFinished(3000)); |
795 | if (consumers.first()->state() == QProcess::Running || |
796 | consumers.first()->exitStatus() != QProcess::NormalExit || |
797 | consumers.first()->exitCode() != 0) { |
798 | consumerFailed = true; |
799 | } |
800 | delete consumers.takeFirst(); |
801 | } |
802 | QCOMPARE(consumerFailed, false); |
803 | QCOMPARE(failedProcesses, (unsigned int)(0)); |
804 | |
805 | // tell the producer to exit now |
806 | producer.write(data: "" , len: 1); |
807 | producer.waitForBytesWritten(); |
808 | QVERIFY(producer.waitForFinished(5000)); |
809 | #endif |
810 | } |
811 | |
812 | void tst_QSharedMemory::uniqueKey_data() |
813 | { |
814 | QTest::addColumn<QString>(name: "key1" ); |
815 | QTest::addColumn<QString>(name: "key2" ); |
816 | |
817 | QTest::newRow(dataTag: "null == null" ) << QString() << QString(); |
818 | QTest::newRow(dataTag: "key == key" ) << QString("key" ) << QString("key" ); |
819 | QTest::newRow(dataTag: "key1 == key1" ) << QString("key1" ) << QString("key1" ); |
820 | QTest::newRow(dataTag: "key != key1" ) << QString("key" ) << QString("key1" ); |
821 | QTest::newRow(dataTag: "ke1y != key1" ) << QString("ke1y" ) << QString("key1" ); |
822 | QTest::newRow(dataTag: "key1 != key2" ) << QString("key1" ) << QString("key2" ); |
823 | QTest::newRow(dataTag: "Noël -> Nol" ) << QString::fromUtf8(str: "N\xc3\xabl" ) << QString("Nol" ); |
824 | } |
825 | |
826 | void tst_QSharedMemory::uniqueKey() |
827 | { |
828 | QFETCH(QString, key1); |
829 | QFETCH(QString, key2); |
830 | |
831 | QSharedMemory sm1(key1); |
832 | QSharedMemory sm2(key2); |
833 | |
834 | bool setEqual = (key1 == key2); |
835 | bool keyEqual = (sm1.key() == sm2.key()); |
836 | bool nativeEqual = (sm1.nativeKey() == sm2.nativeKey()); |
837 | |
838 | QCOMPARE(keyEqual, setEqual); |
839 | QCOMPARE(nativeEqual, setEqual); |
840 | } |
841 | |
842 | QTEST_MAIN(tst_QSharedMemory) |
843 | #include "tst_qsharedmemory.moc" |
844 | |
845 | |