1/*
2 Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
3
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
7
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
10 freely.
11*/
12
13#include <SDL3/SDL.h>
14#include <SDL3/SDL_main.h>
15#include <SDL3/SDL_test.h>
16
17/*
18 Absolutely basic tests just to see if we get the expected value
19 after calling each function.
20*/
21
22static const char *
23tf(bool _tf)
24{
25 static const char *t = "TRUE";
26 static const char *f = "FALSE";
27
28 if (_tf) {
29 return t;
30 }
31
32 return f;
33}
34
35static void RunBasicTest(void)
36{
37 int value;
38 SDL_SpinLock lock = 0;
39
40 SDL_AtomicInt v;
41 bool tfret = false;
42
43 SDL_Log("\nspin lock---------------------------------------\n\n");
44
45 SDL_LockSpinlock(&lock);
46 SDL_Log("AtomicLock lock=%d\n", lock);
47 SDL_UnlockSpinlock(&lock);
48 SDL_Log("AtomicUnlock lock=%d\n", lock);
49
50 SDL_Log("\natomic -----------------------------------------\n\n");
51
52 SDL_SetAtomicInt(&v, 0);
53 tfret = SDL_SetAtomicInt(&v, 10) == 0;
54 SDL_Log("AtomicSet(10) tfret=%s val=%d\n", tf(tfret), SDL_GetAtomicInt(&v));
55 tfret = SDL_AddAtomicInt(&v, 10) == 10;
56 SDL_Log("AtomicAdd(10) tfret=%s val=%d\n", tf(tfret), SDL_GetAtomicInt(&v));
57
58 SDL_SetAtomicInt(&v, 0);
59 SDL_AtomicIncRef(&v);
60 tfret = (SDL_GetAtomicInt(&v) == 1);
61 SDL_Log("AtomicIncRef() tfret=%s val=%d\n", tf(tfret), SDL_GetAtomicInt(&v));
62 SDL_AtomicIncRef(&v);
63 tfret = (SDL_GetAtomicInt(&v) == 2);
64 SDL_Log("AtomicIncRef() tfret=%s val=%d\n", tf(tfret), SDL_GetAtomicInt(&v));
65 tfret = (SDL_AtomicDecRef(&v) == false);
66 SDL_Log("AtomicDecRef() tfret=%s val=%d\n", tf(tfret), SDL_GetAtomicInt(&v));
67 tfret = (SDL_AtomicDecRef(&v) == true);
68 SDL_Log("AtomicDecRef() tfret=%s val=%d\n", tf(tfret), SDL_GetAtomicInt(&v));
69
70 SDL_SetAtomicInt(&v, 10);
71 tfret = (SDL_CompareAndSwapAtomicInt(&v, 0, 20) == false);
72 SDL_Log("AtomicCAS() tfret=%s val=%d\n", tf(tfret), SDL_GetAtomicInt(&v));
73 value = SDL_GetAtomicInt(&v);
74 tfret = (SDL_CompareAndSwapAtomicInt(&v, value, 20) == true);
75 SDL_Log("AtomicCAS() tfret=%s val=%d\n", tf(tfret), SDL_GetAtomicInt(&v));
76}
77
78/**************************************************************************/
79/* Atomic operation test
80 * Adapted with permission from code by Michael Davidsaver at:
81 * http://bazaar.launchpad.net/~mdavidsaver/epics-base/atomic/revision/12105#src/libCom/test/epicsAtomicTest.c
82 * Original copyright 2010 Brookhaven Science Associates as operator of Brookhaven National Lab
83 * http://www.aps.anl.gov/epics/license/open.php
84 */
85
86/* Tests semantics of atomic operations. Also a stress test
87 * to see if they are really atomic.
88 *
89 * Several threads adding to the same variable.
90 * at the end the value is compared with the expected
91 * and with a non-atomic counter.
92 */
93
94/* Number of concurrent incrementers */
95#define NThreads 2
96#define CountInc 100
97#define VALBITS (sizeof(atomicValue) * 8)
98
99#define atomicValue int
100#define CountTo ((atomicValue)((unsigned int)(1 << (VALBITS - 1)) - 1))
101#define NInter (CountTo / CountInc / NThreads)
102#define Expect (CountTo - NInter * CountInc * NThreads)
103
104enum
105{
106 CountTo_GreaterThanZero = CountTo > 0,
107};
108SDL_COMPILE_TIME_ASSERT(size, CountTo_GreaterThanZero); /* check for rollover */
109
110static SDL_AtomicInt good = { 42 };
111static atomicValue bad = 42;
112static SDL_AtomicInt threadsRunning;
113static SDL_Semaphore *threadDone;
114
115static int SDLCALL adder(void *junk)
116{
117 unsigned long N = NInter;
118 SDL_Log("Thread subtracting %d %lu times\n", CountInc, N);
119 while (N--) {
120 SDL_AddAtomicInt(&good, -CountInc);
121 bad -= CountInc;
122 }
123 SDL_AddAtomicInt(&threadsRunning, -1);
124 SDL_SignalSemaphore(threadDone);
125 return 0;
126}
127
128static void runAdder(void)
129{
130 Uint64 start, end;
131 int i;
132 SDL_Thread *threads[NThreads];
133
134 start = SDL_GetTicksNS();
135
136 threadDone = SDL_CreateSemaphore(0);
137
138 SDL_SetAtomicInt(&threadsRunning, NThreads);
139
140 for (i = 0; i < NThreads; i++) {
141 threads[i] = SDL_CreateThread(adder, "Adder", NULL);
142 }
143
144 while (SDL_GetAtomicInt(&threadsRunning) > 0) {
145 SDL_WaitSemaphore(threadDone);
146 }
147
148 for (i = 0; i < NThreads; i++) {
149 SDL_WaitThread(threads[i], NULL);
150 }
151
152 SDL_DestroySemaphore(threadDone);
153
154 end = SDL_GetTicksNS();
155
156 SDL_Log("Finished in %f sec\n", (end - start) / 1000000000.0);
157}
158
159static void RunEpicTest(void)
160{
161 int b;
162 atomicValue v;
163
164 SDL_Log("\nepic test---------------------------------------\n\n");
165
166 SDL_Log("Size asserted to be >= 32-bit\n");
167 SDL_assert(sizeof(atomicValue) >= 4);
168
169 SDL_Log("Check static initializer\n");
170 v = SDL_GetAtomicInt(&good);
171 SDL_assert(v == 42);
172
173 SDL_assert(bad == 42);
174
175 SDL_Log("Test negative values\n");
176 SDL_SetAtomicInt(&good, -5);
177 v = SDL_GetAtomicInt(&good);
178 SDL_assert(v == -5);
179
180 SDL_Log("Verify maximum value\n");
181 SDL_SetAtomicInt(&good, CountTo);
182 v = SDL_GetAtomicInt(&good);
183 SDL_assert(v == CountTo);
184
185 SDL_Log("Test compare and exchange\n");
186
187 b = SDL_CompareAndSwapAtomicInt(&good, 500, 43);
188 SDL_assert(!b); /* no swap since CountTo!=500 */
189 v = SDL_GetAtomicInt(&good);
190 SDL_assert(v == CountTo); /* ensure no swap */
191
192 b = SDL_CompareAndSwapAtomicInt(&good, CountTo, 44);
193 SDL_assert(!!b); /* will swap */
194 v = SDL_GetAtomicInt(&good);
195 SDL_assert(v == 44);
196
197 SDL_Log("Test Add\n");
198
199 v = SDL_AddAtomicInt(&good, 1);
200 SDL_assert(v == 44);
201 v = SDL_GetAtomicInt(&good);
202 SDL_assert(v == 45);
203
204 v = SDL_AddAtomicInt(&good, 10);
205 SDL_assert(v == 45);
206 v = SDL_GetAtomicInt(&good);
207 SDL_assert(v == 55);
208
209 SDL_Log("Test Add (Negative values)\n");
210
211 v = SDL_AddAtomicInt(&good, -20);
212 SDL_assert(v == 55);
213 v = SDL_GetAtomicInt(&good);
214 SDL_assert(v == 35);
215
216 v = SDL_AddAtomicInt(&good, -50); /* crossing zero down */
217 SDL_assert(v == 35);
218 v = SDL_GetAtomicInt(&good);
219 SDL_assert(v == -15);
220
221 v = SDL_AddAtomicInt(&good, 30); /* crossing zero up */
222 SDL_assert(v == -15);
223 v = SDL_GetAtomicInt(&good);
224 SDL_assert(v == 15);
225
226 SDL_Log("Reset before count down test\n");
227 SDL_SetAtomicInt(&good, CountTo);
228 v = SDL_GetAtomicInt(&good);
229 SDL_assert(v == CountTo);
230
231 bad = CountTo;
232 SDL_assert(bad == CountTo);
233
234 SDL_Log("Counting down from %d, Expect %d remaining\n", CountTo, Expect);
235 runAdder();
236
237 v = SDL_GetAtomicInt(&good);
238 SDL_Log("Atomic %d Non-Atomic %d\n", v, bad);
239 SDL_assert(v == Expect);
240 /* We can't guarantee that bad != Expect, this would happen on a single core system, for example. */
241 /*SDL_assert(bad != Expect);*/
242}
243
244/* End atomic operation test */
245/**************************************************************************/
246
247/**************************************************************************/
248/* Lock-free FIFO test */
249
250/* This is useful to test the impact of another thread locking the queue
251 entirely for heavy-weight manipulation.
252 */
253#define TEST_SPINLOCK_FIFO
254
255#define NUM_READERS 4
256#define NUM_WRITERS 4
257#define EVENTS_PER_WRITER 1000000
258
259/* The number of entries must be a power of 2 */
260#define MAX_ENTRIES 256
261#define WRAP_MASK (MAX_ENTRIES - 1)
262
263typedef struct
264{
265 SDL_AtomicInt sequence;
266 SDL_Event event;
267} SDL_EventQueueEntry;
268
269typedef struct
270{
271 SDL_EventQueueEntry entries[MAX_ENTRIES];
272
273 char cache_pad1[SDL_CACHELINE_SIZE - ((sizeof(SDL_EventQueueEntry) * MAX_ENTRIES) % SDL_CACHELINE_SIZE)];
274
275 SDL_AtomicInt enqueue_pos;
276
277 char cache_pad2[SDL_CACHELINE_SIZE - sizeof(SDL_AtomicInt)];
278
279 SDL_AtomicInt dequeue_pos;
280
281 char cache_pad3[SDL_CACHELINE_SIZE - sizeof(SDL_AtomicInt)];
282
283#ifdef TEST_SPINLOCK_FIFO
284 SDL_SpinLock lock;
285 SDL_AtomicInt rwcount;
286 SDL_AtomicInt watcher;
287
288 char cache_pad4[SDL_CACHELINE_SIZE - sizeof(SDL_SpinLock) - 2 * sizeof(SDL_AtomicInt)];
289#endif
290
291 SDL_AtomicInt active;
292
293 /* Only needed for the mutex test */
294 SDL_Mutex *mutex;
295
296} SDL_EventQueue;
297
298static void InitEventQueue(SDL_EventQueue *queue)
299{
300 int i;
301
302 for (i = 0; i < MAX_ENTRIES; ++i) {
303 SDL_SetAtomicInt(&queue->entries[i].sequence, i);
304 }
305 SDL_SetAtomicInt(&queue->enqueue_pos, 0);
306 SDL_SetAtomicInt(&queue->dequeue_pos, 0);
307#ifdef TEST_SPINLOCK_FIFO
308 queue->lock = 0;
309 SDL_SetAtomicInt(&queue->rwcount, 0);
310 SDL_SetAtomicInt(&queue->watcher, 0);
311#endif
312 SDL_SetAtomicInt(&queue->active, 1);
313}
314
315static bool EnqueueEvent_LockFree(SDL_EventQueue *queue, const SDL_Event *event)
316{
317 SDL_EventQueueEntry *entry;
318 unsigned queue_pos;
319 unsigned entry_seq;
320 int delta;
321 bool status;
322
323#ifdef TEST_SPINLOCK_FIFO
324 /* This is a gate so an external thread can lock the queue */
325 SDL_LockSpinlock(&queue->lock);
326 SDL_assert(SDL_GetAtomicInt(&queue->watcher) == 0);
327 SDL_AtomicIncRef(&queue->rwcount);
328 SDL_UnlockSpinlock(&queue->lock);
329#endif
330
331 queue_pos = (unsigned)SDL_GetAtomicInt(&queue->enqueue_pos);
332 for (;;) {
333 entry = &queue->entries[queue_pos & WRAP_MASK];
334 entry_seq = (unsigned)SDL_GetAtomicInt(&entry->sequence);
335
336 delta = (int)(entry_seq - queue_pos);
337 if (delta == 0) {
338 /* The entry and the queue position match, try to increment the queue position */
339 if (SDL_CompareAndSwapAtomicInt(&queue->enqueue_pos, (int)queue_pos, (int)(queue_pos + 1))) {
340 /* We own the object, fill it! */
341 entry->event = *event;
342 SDL_SetAtomicInt(&entry->sequence, (int)(queue_pos + 1));
343 status = true;
344 break;
345 }
346 } else if (delta < 0) {
347 /* We ran into an old queue entry, which means it still needs to be dequeued */
348 status = false;
349 break;
350 } else {
351 /* We ran into a new queue entry, get the new queue position */
352 queue_pos = (unsigned)SDL_GetAtomicInt(&queue->enqueue_pos);
353 }
354 }
355
356#ifdef TEST_SPINLOCK_FIFO
357 (void)SDL_AtomicDecRef(&queue->rwcount);
358#endif
359 return status;
360}
361
362static bool DequeueEvent_LockFree(SDL_EventQueue *queue, SDL_Event *event)
363{
364 SDL_EventQueueEntry *entry;
365 unsigned queue_pos;
366 unsigned entry_seq;
367 int delta;
368 bool status;
369
370#ifdef TEST_SPINLOCK_FIFO
371 /* This is a gate so an external thread can lock the queue */
372 SDL_LockSpinlock(&queue->lock);
373 SDL_assert(SDL_GetAtomicInt(&queue->watcher) == 0);
374 SDL_AtomicIncRef(&queue->rwcount);
375 SDL_UnlockSpinlock(&queue->lock);
376#endif
377
378 queue_pos = (unsigned)SDL_GetAtomicInt(&queue->dequeue_pos);
379 for (;;) {
380 entry = &queue->entries[queue_pos & WRAP_MASK];
381 entry_seq = (unsigned)SDL_GetAtomicInt(&entry->sequence);
382
383 delta = (int)(entry_seq - (queue_pos + 1));
384 if (delta == 0) {
385 /* The entry and the queue position match, try to increment the queue position */
386 if (SDL_CompareAndSwapAtomicInt(&queue->dequeue_pos, (int)queue_pos, (int)(queue_pos + 1))) {
387 /* We own the object, fill it! */
388 *event = entry->event;
389 SDL_SetAtomicInt(&entry->sequence, (int)(queue_pos + MAX_ENTRIES));
390 status = true;
391 break;
392 }
393 } else if (delta < 0) {
394 /* We ran into an old queue entry, which means we've hit empty */
395 status = false;
396 break;
397 } else {
398 /* We ran into a new queue entry, get the new queue position */
399 queue_pos = (unsigned)SDL_GetAtomicInt(&queue->dequeue_pos);
400 }
401 }
402
403#ifdef TEST_SPINLOCK_FIFO
404 (void)SDL_AtomicDecRef(&queue->rwcount);
405#endif
406 return status;
407}
408
409static bool EnqueueEvent_Mutex(SDL_EventQueue *queue, const SDL_Event *event)
410{
411 SDL_EventQueueEntry *entry;
412 unsigned queue_pos;
413 unsigned entry_seq;
414 int delta;
415 bool status = false;
416
417 SDL_LockMutex(queue->mutex);
418
419 queue_pos = (unsigned)queue->enqueue_pos.value;
420 entry = &queue->entries[queue_pos & WRAP_MASK];
421 entry_seq = (unsigned)entry->sequence.value;
422
423 delta = (int)(entry_seq - queue_pos);
424 if (delta == 0) {
425 ++queue->enqueue_pos.value;
426
427 /* We own the object, fill it! */
428 entry->event = *event;
429 entry->sequence.value = (int)(queue_pos + 1);
430 status = true;
431 } else if (delta < 0) {
432 /* We ran into an old queue entry, which means it still needs to be dequeued */
433 } else {
434 SDL_Log("ERROR: mutex failed!\n");
435 }
436
437 SDL_UnlockMutex(queue->mutex);
438
439 return status;
440}
441
442static bool DequeueEvent_Mutex(SDL_EventQueue *queue, SDL_Event *event)
443{
444 SDL_EventQueueEntry *entry;
445 unsigned queue_pos;
446 unsigned entry_seq;
447 int delta;
448 bool status = false;
449
450 SDL_LockMutex(queue->mutex);
451
452 queue_pos = (unsigned)queue->dequeue_pos.value;
453 entry = &queue->entries[queue_pos & WRAP_MASK];
454 entry_seq = (unsigned)entry->sequence.value;
455
456 delta = (int)(entry_seq - (queue_pos + 1));
457 if (delta == 0) {
458 ++queue->dequeue_pos.value;
459
460 /* We own the object, fill it! */
461 *event = entry->event;
462 entry->sequence.value = (int)(queue_pos + MAX_ENTRIES);
463 status = true;
464 } else if (delta < 0) {
465 /* We ran into an old queue entry, which means we've hit empty */
466 } else {
467 SDL_Log("ERROR: mutex failed!\n");
468 }
469
470 SDL_UnlockMutex(queue->mutex);
471
472 return status;
473}
474
475typedef struct
476{
477 SDL_EventQueue *queue;
478 int index;
479 char padding1[SDL_CACHELINE_SIZE - (sizeof(SDL_EventQueue *) + sizeof(int)) % SDL_CACHELINE_SIZE];
480 int waits;
481 bool lock_free;
482 char padding2[SDL_CACHELINE_SIZE - sizeof(int) - sizeof(bool)];
483 SDL_Thread *thread;
484} WriterData;
485
486typedef struct
487{
488 SDL_EventQueue *queue;
489 int counters[NUM_WRITERS];
490 int waits;
491 bool lock_free;
492 char padding[SDL_CACHELINE_SIZE - (sizeof(SDL_EventQueue *) + sizeof(int) * NUM_WRITERS + sizeof(int) + sizeof(bool)) % SDL_CACHELINE_SIZE];
493 SDL_Thread *thread;
494} ReaderData;
495
496static int SDLCALL FIFO_Writer(void *_data)
497{
498 WriterData *data = (WriterData *)_data;
499 SDL_EventQueue *queue = data->queue;
500 int i;
501 SDL_Event event;
502
503 event.type = SDL_EVENT_USER;
504 event.user.windowID = 0;
505 event.user.code = 0;
506 event.user.data1 = data;
507 event.user.data2 = NULL;
508
509 if (data->lock_free) {
510 for (i = 0; i < EVENTS_PER_WRITER; ++i) {
511 event.user.code = i;
512 while (!EnqueueEvent_LockFree(queue, &event)) {
513 ++data->waits;
514 SDL_Delay(0);
515 }
516 }
517 } else {
518 for (i = 0; i < EVENTS_PER_WRITER; ++i) {
519 event.user.code = i;
520 while (!EnqueueEvent_Mutex(queue, &event)) {
521 ++data->waits;
522 SDL_Delay(0);
523 }
524 }
525 }
526 return 0;
527}
528
529static int SDLCALL FIFO_Reader(void *_data)
530{
531 ReaderData *data = (ReaderData *)_data;
532 SDL_EventQueue *queue = data->queue;
533 SDL_Event event;
534
535 if (data->lock_free) {
536 for (;;) {
537 if (DequeueEvent_LockFree(queue, &event)) {
538 WriterData *writer = (WriterData *)event.user.data1;
539 ++data->counters[writer->index];
540 } else if (SDL_GetAtomicInt(&queue->active)) {
541 ++data->waits;
542 SDL_Delay(0);
543 } else {
544 /* We drained the queue, we're done! */
545 break;
546 }
547 }
548 } else {
549 for (;;) {
550 if (DequeueEvent_Mutex(queue, &event)) {
551 WriterData *writer = (WriterData *)event.user.data1;
552 ++data->counters[writer->index];
553 } else if (SDL_GetAtomicInt(&queue->active)) {
554 ++data->waits;
555 SDL_Delay(0);
556 } else {
557 /* We drained the queue, we're done! */
558 break;
559 }
560 }
561 }
562 return 0;
563}
564
565#ifdef TEST_SPINLOCK_FIFO
566/* This thread periodically locks the queue for no particular reason */
567static int SDLCALL FIFO_Watcher(void *_data)
568{
569 SDL_EventQueue *queue = (SDL_EventQueue *)_data;
570
571 while (SDL_GetAtomicInt(&queue->active)) {
572 SDL_LockSpinlock(&queue->lock);
573 SDL_AtomicIncRef(&queue->watcher);
574 while (SDL_GetAtomicInt(&queue->rwcount) > 0) {
575 SDL_Delay(0);
576 }
577 /* Do queue manipulation here... */
578 (void)SDL_AtomicDecRef(&queue->watcher);
579 SDL_UnlockSpinlock(&queue->lock);
580
581 /* Wait a bit... */
582 SDL_Delay(1);
583 }
584 return 0;
585}
586#endif /* TEST_SPINLOCK_FIFO */
587
588static void RunFIFOTest(bool lock_free)
589{
590 SDL_EventQueue queue;
591 SDL_Thread *fifo_thread = NULL;
592 WriterData writerData[NUM_WRITERS];
593 ReaderData readerData[NUM_READERS];
594 Uint64 start, end;
595 int i, j;
596 int grand_total;
597 char textBuffer[1024];
598 size_t len;
599
600 SDL_Log("\nFIFO test---------------------------------------\n\n");
601 SDL_Log("Mode: %s\n", lock_free ? "LockFree" : "Mutex");
602
603 SDL_memset(&queue, 0xff, sizeof(queue));
604
605 InitEventQueue(&queue);
606 if (!lock_free) {
607 queue.mutex = SDL_CreateMutex();
608 }
609
610 start = SDL_GetTicksNS();
611
612#ifdef TEST_SPINLOCK_FIFO
613 /* Start a monitoring thread */
614 if (lock_free) {
615 fifo_thread = SDL_CreateThread(FIFO_Watcher, "FIFOWatcher", &queue);
616 }
617#endif
618
619 /* Start the readers first */
620 SDL_Log("Starting %d readers\n", NUM_READERS);
621 SDL_zeroa(readerData);
622 for (i = 0; i < NUM_READERS; ++i) {
623 char name[64];
624 (void)SDL_snprintf(name, sizeof(name), "FIFOReader%d", i);
625 readerData[i].queue = &queue;
626 readerData[i].lock_free = lock_free;
627 readerData[i].thread = SDL_CreateThread(FIFO_Reader, name, &readerData[i]);
628 }
629
630 /* Start up the writers */
631 SDL_Log("Starting %d writers\n", NUM_WRITERS);
632 SDL_zeroa(writerData);
633 for (i = 0; i < NUM_WRITERS; ++i) {
634 char name[64];
635 (void)SDL_snprintf(name, sizeof(name), "FIFOWriter%d", i);
636 writerData[i].queue = &queue;
637 writerData[i].index = i;
638 writerData[i].lock_free = lock_free;
639 writerData[i].thread = SDL_CreateThread(FIFO_Writer, name, &writerData[i]);
640 }
641
642 /* Wait for the writers */
643 for (i = 0; i < NUM_WRITERS; ++i) {
644 SDL_WaitThread(writerData[i].thread, NULL);
645 }
646
647 /* Shut down the queue so readers exit */
648 SDL_SetAtomicInt(&queue.active, 0);
649
650 /* Wait for the readers */
651 for (i = 0; i < NUM_READERS; ++i) {
652 SDL_WaitThread(readerData[i].thread, NULL);
653 }
654
655 end = SDL_GetTicksNS();
656
657 /* Wait for the FIFO thread */
658 if (fifo_thread) {
659 SDL_WaitThread(fifo_thread, NULL);
660 }
661
662 if (!lock_free) {
663 SDL_DestroyMutex(queue.mutex);
664 }
665
666 SDL_Log("Finished in %f sec\n", (end - start) / 1000000000.0);
667
668 SDL_Log("\n");
669 for (i = 0; i < NUM_WRITERS; ++i) {
670 SDL_Log("Writer %d wrote %d events, had %d waits\n", i, EVENTS_PER_WRITER, writerData[i].waits);
671 }
672 SDL_Log("Writers wrote %d total events\n", NUM_WRITERS * EVENTS_PER_WRITER);
673
674 /* Print a breakdown of which readers read messages from which writer */
675 SDL_Log("\n");
676 grand_total = 0;
677 for (i = 0; i < NUM_READERS; ++i) {
678 int total = 0;
679 for (j = 0; j < NUM_WRITERS; ++j) {
680 total += readerData[i].counters[j];
681 }
682 grand_total += total;
683 SDL_Log("Reader %d read %d events, had %d waits\n", i, total, readerData[i].waits);
684 (void)SDL_snprintf(textBuffer, sizeof(textBuffer), " { ");
685 for (j = 0; j < NUM_WRITERS; ++j) {
686 if (j > 0) {
687 len = SDL_strlen(textBuffer);
688 (void)SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, ", ");
689 }
690 len = SDL_strlen(textBuffer);
691 (void)SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, "%d", readerData[i].counters[j]);
692 }
693 len = SDL_strlen(textBuffer);
694 (void)SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, " }\n");
695 SDL_Log("%s", textBuffer);
696 }
697 SDL_Log("Readers read %d total events\n", grand_total);
698}
699
700/* End FIFO test */
701/**************************************************************************/
702
703int main(int argc, char *argv[])
704{
705 SDLTest_CommonState *state;
706 int i;
707 bool enable_threads = true;
708
709 /* Initialize test framework */
710 state = SDLTest_CommonCreateState(argv, 0);
711 if (!state) {
712 return 1;
713 }
714
715 /* Parse commandline */
716 for (i = 1; i < argc;) {
717 int consumed;
718
719 consumed = SDLTest_CommonArg(state, i);
720 if (consumed == 0) {
721 consumed = -1;
722 if (SDL_strcasecmp(argv[i], "--no-threads") == 0) {
723 enable_threads = false;
724 consumed = 1;
725 }
726 }
727 if (consumed < 0) {
728 static const char *options[] = {
729 "[--no-threads]",
730 NULL
731 };
732 SDLTest_CommonLogUsage(state, argv[0], options);
733 return 1;
734 }
735 i += consumed;
736 }
737
738 RunBasicTest();
739
740 if (SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "SDL_TESTS_QUICK") != NULL) {
741 SDL_Log("Not running slower tests");
742 return 0;
743 }
744
745 if (enable_threads) {
746 RunEpicTest();
747 }
748/* This test is really slow, so don't run it by default */
749#if 0
750 RunFIFOTest(false);
751#endif
752 RunFIFOTest(true);
753 SDL_Quit();
754 SDLTest_CommonDestroyState(state);
755 return 0;
756}