| #include "unity/unity.h" |
| #include "zlib.h" |
|
|
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdint.h> |
| #include <pthread.h> |
|
|
| |
| |
| #if defined(__STDC__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) |
| #define HAVE_C11_ATOMICS 1 |
| #else |
| #define HAVE_C11_ATOMICS 0 |
| #endif |
|
|
| |
| #ifndef ONCE_TEST_THREADS |
| #define ONCE_TEST_THREADS 8 |
| #endif |
|
|
| |
| typedef struct { |
| volatile int start_flag; |
| } start_sync_t; |
|
|
| typedef struct { |
| start_sync_t *sync; |
| const unsigned char *buf; |
| size_t len; |
| const void *table_ptr; |
| unsigned long crc_out; |
| } thread_arg_t; |
|
|
| static void busy_wait_until_start(start_sync_t *sync) { |
| |
| while (!sync->start_flag) { } |
| } |
|
|
| static void *thread_get_table(void *arg) { |
| thread_arg_t *a = (thread_arg_t *)arg; |
| busy_wait_until_start(a->sync); |
| |
| a->table_ptr = (const void *)get_crc_table(); |
| |
| if (a->buf && a->len > 0) { |
| a->crc_out = crc32(0L, a->buf, (uInt)a->len); |
| } else { |
| a->crc_out = 0UL; |
| } |
| return NULL; |
| } |
|
|
| static void *thread_do_crc(void *arg) { |
| thread_arg_t *a = (thread_arg_t *)arg; |
| busy_wait_until_start(a->sync); |
| |
| unsigned long crc = 0L; |
| for (int i = 0; i < 1000; i++) { |
| crc = crc32(0L, a->buf, (uInt)a->len); |
| } |
| a->crc_out = crc; |
| |
| a->table_ptr = (const void *)get_crc_table(); |
| return NULL; |
| } |
|
|
| void setUp(void) { |
| |
| |
| } |
|
|
| void tearDown(void) { |
| |
| } |
|
|
| |
| |
| void test_once_concurrent_initialization_via_get_crc_table(void) { |
| #if HAVE_C11_ATOMICS |
| pthread_t tids[ONCE_TEST_THREADS]; |
| thread_arg_t args[ONCE_TEST_THREADS]; |
| start_sync_t sync; |
| sync.start_flag = 0; |
|
|
| static const unsigned char data[] = "zlib once() concurrent init"; |
| for (int i = 0; i < ONCE_TEST_THREADS; i++) { |
| args[i].sync = &sync; |
| args[i].buf = data; |
| args[i].len = strlen((const char *)data); |
| args[i].table_ptr = NULL; |
| args[i].crc_out = 0; |
| int rc = pthread_create(&tids[i], NULL, thread_get_table, &args[i]); |
| TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "pthread_create failed"); |
| } |
|
|
| |
| sync.start_flag = 1; |
|
|
| for (int i = 0; i < ONCE_TEST_THREADS; i++) { |
| pthread_join(tids[i], NULL); |
| } |
|
|
| |
| const void *ptr0 = args[0].table_ptr; |
| TEST_ASSERT_NOT_NULL(ptr0); |
| for (int i = 1; i < ONCE_TEST_THREADS; i++) { |
| TEST_ASSERT_EQUAL_PTR(ptr0, args[i].table_ptr); |
| } |
|
|
| |
| const void *ptr_after = (const void *)get_crc_table(); |
| TEST_ASSERT_EQUAL_PTR(ptr0, ptr_after); |
| #else |
| TEST_IGNORE_MESSAGE("Concurrent initialization is not supported without C11 atomics; skipping."); |
| #endif |
| } |
|
|
| |
| void test_once_crc_known_value_single_thread(void) { |
| const unsigned char msg[] = "123456789"; |
| unsigned long crc = crc32(0L, msg, (uInt)strlen((const char *)msg)); |
| TEST_ASSERT_EQUAL_HEX32(0xCBF43926UL, crc); |
| } |
|
|
| |
| void test_once_pointer_stability_single_thread(void) { |
| const void *p1 = (const void *)get_crc_table(); |
| TEST_ASSERT_NOT_NULL(p1); |
|
|
| for (int i = 0; i < 1000; i++) { |
| const void *pi = (const void *)get_crc_table(); |
| TEST_ASSERT_EQUAL_PTR(p1, pi); |
| } |
| } |
|
|
| |
| |
| void test_once_concurrent_crc_after_preinit(void) { |
| |
| const void *ptable = (const void *)get_crc_table(); |
| TEST_ASSERT_NOT_NULL(ptable); |
|
|
| pthread_t tids[ONCE_TEST_THREADS]; |
| thread_arg_t args[ONCE_TEST_THREADS]; |
| start_sync_t sync; |
| sync.start_flag = 0; |
|
|
| static const unsigned char msg[] = "The quick brown fox jumps over the lazy dog"; |
| |
| unsigned long expected = crc32(0L, msg, (uInt)strlen((const char *)msg)); |
|
|
| for (int i = 0; i < ONCE_TEST_THREADS; i++) { |
| args[i].sync = &sync; |
| args[i].buf = msg; |
| args[i].len = strlen((const char *)msg); |
| args[i].table_ptr = NULL; |
| args[i].crc_out = 0; |
| int rc = pthread_create(&tids[i], NULL, thread_do_crc, &args[i]); |
| TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "pthread_create failed"); |
| } |
|
|
| |
| sync.start_flag = 1; |
|
|
| for (int i = 0; i < ONCE_TEST_THREADS; i++) { |
| pthread_join(tids[i], NULL); |
| } |
|
|
| for (int i = 0; i < ONCE_TEST_THREADS; i++) { |
| TEST_ASSERT_EQUAL_HEX32(expected, args[i].crc_out); |
| TEST_ASSERT_EQUAL_PTR(ptable, args[i].table_ptr); |
| } |
| } |
|
|
| int main(void) { |
| UNITY_BEGIN(); |
| RUN_TEST(test_once_concurrent_initialization_via_get_crc_table); |
| RUN_TEST(test_once_crc_known_value_single_thread); |
| RUN_TEST(test_once_pointer_stability_single_thread); |
| RUN_TEST(test_once_concurrent_crc_after_preinit); |
| return UNITY_END(); |
| } |