[vlc-devel] [PATCH] test: add thread test

Marvin Scholz epirat07 at gmail.com
Fri Mar 20 16:09:51 CET 2020


Hi, thanks for the review. See replies inline:

On 20 Mar 2020, at 13:48, Rémi Denis-Courmont wrote:

> Le torstaina 19. maaliskuuta 2020, 20.44.03 EET Marvin Scholz a écrit 
> :
>> ---
>>  src/Makefile.am   |   4 +-
>>  src/test/thread.c | 335 
>> ++++++++++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 338 insertions(+), 1 deletion(-)
>>  create mode 100644 src/test/thread.c
>>
>> diff --git a/src/Makefile.am b/src/Makefile.am
>> index 2b4dfcb7dd..1f5df99bb7 100644
>> --- a/src/Makefile.am
>> +++ b/src/Makefile.am
>> @@ -586,7 +586,8 @@ check_PROGRAMS = \
>>  	test_playlist \
>>  	test_randomizer \
>>  	test_media_source \
>> -	test_extensions
>> +	test_extensions \
>> +	test_thread
>>
>>  TESTS = $(check_PROGRAMS) check_symbols
>>
>> @@ -632,6 +633,7 @@ test_media_source_CFLAGS = -DTEST_MEDIA_SOURCE
>>  test_media_source_SOURCES = media_source/test.c \
>>  	media_source/media_source.c \
>>  	media_source/media_tree.c
>> +test_thread_SOURCES = test/thread.c
>>
>>  AM_LDFLAGS = -no-install
>>  LDADD = libvlccore.la \
>> diff --git a/src/test/thread.c b/src/test/thread.c
>> new file mode 100644
>> index 0000000000..87956c0013
>> --- /dev/null
>> +++ b/src/test/thread.c
>> @@ -0,0 +1,335 @@
>> +/**************************************************************************
>> *** + * thread.c: Test for thread API
>> +
>> ***************************************************************************
>> ** + * Copyright (C) 2020 Marvin Scholz
>> + *
>> + * This program is free software; you can redistribute it and/or 
>> modify it
>> + * under the terms of the GNU Lesser General Public License as 
>> published by
>> + * the Free Software Foundation; either version 2.1 of the License, 
>> or + *
>> (at your option) any later version.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU Lesser General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU Lesser General Public 
>> License
>> + * along with this program; if not, write to the Free Software 
>> Foundation,
>> + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 
>> +
>> ***************************************************************************
>> **/ +
>> +#ifdef HAVE_CONFIG_H
>> +# include "config.h"
>> +#endif
>> +
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <sys/errno.h>
>
> Uh? Do you mean errno.h ?

Indeed, changed.

>
>> +#undef NDEBUG
>> +#include <assert.h>
>> +
>> +#include <vlc_common.h>
>> +
>> +static int thread_data_magic = 0x1234;
>> +static int thread_return_magic = 0x4321;
>> +
>> +// Join thread and verify return
>> +#define TEST_THREAD_JOIN(th, eret)  \
>> +    do {                            \
>> +        void *r;                    \
>> +        vlc_join(th, &r);           \
>> +        assert(r == eret);          \
>> +    } while (0)
>> +
>> +// Cancels thread, joins and verifies return
>> +#define TEST_THREAD_CANCEL_JOIN(th)                 \
>> +    do {                                            \
>> +        vlc_cancel(th);                             \
>> +        TEST_THREAD_JOIN(th, VLC_THREAD_CANCELED);  \
>> +    } while (0)
>> +
>> +// Join thread and verify magic return value
>> +#define TEST_THREAD_JOIN_MAGIC(th) \
>> +    TEST_THREAD_JOIN(th, &thread_return_magic)
>> +
>> +// Spawn thread
>> +#define TEST_THREAD_CLONE(th, f, data) \
>> +    assert(vlc_clone(th, f, data, VLC_THREAD_PRIORITY_LOW) == 0)
>> +
>> +// Spawn thread with magic data
>> +#define TEST_THREAD_CLONE_MAGIC(th, f) \
>> +    TEST_THREAD_CLONE(th, f, &thread_data_magic)
>> +
>> +struct cond_data
>> +{
>> +    int state;
>> +    vlc_mutex_t mutex;
>> +    vlc_cond_t  cond;
>> +};
>> +
>> +
>> +/*
>> + * Thread entry point functions
>> + *
>> + * All of these should return &thread_return_magic which
>> + * is used by TEST_THREAD_JOIN_MAGIC.
>> + */
>> +
>> +static void *thread_func_noop(void *data)
>> +{
>> +    assert(data == &thread_data_magic);
>> +    return &thread_return_magic;
>> +}
>> +
>> +static void *thread_func_loop(void *data)
>> +{
>> +    assert(data == &thread_data_magic);
>> +    while(1) {
>> +        vlc_testcancel();
>> +    }
>> +    return &thread_return_magic;
>> +}
>> +
>> +static void thread_func_cleanup(void *data)
>> +{
>> +    int *cleanup_state = data;
>> +    assert(*cleanup_state == 0);
>> +    *cleanup_state = 1;
>> +}
>> +
>> +static void *thread_func_loop_cleanup(void *data)
>> +{
>> +    int *cleanup_state = data;
>> +    assert(*cleanup_state == 0);
>> +
>> +    vlc_cleanup_push(thread_func_cleanup, data);
>> +    while(1) {
>> +        vlc_testcancel();
>> +    }
>> +    vlc_cleanup_pop();
>> +
>> +    return &thread_return_magic;
>> +}
>> +
>> +static void *thread_func_cond_states(void *ptr)
>> +{
>> +    struct cond_data *data = ptr;
>> +
>> +    // Change to state 1
>> +    vlc_mutex_lock(&data->mutex);
>> +    assert(data->state == 0);
>> +    data->state = 1;
>> +    vlc_cond_signal(&data->cond);
>> +    vlc_mutex_unlock(&data->mutex);
>> +
>> +    // Wait for state 2
>> +    vlc_mutex_lock(&data->mutex);
>> +    mutex_cleanup_push(&data->mutex);
>> +    while(data->state != 2) {
>> +        vlc_cond_wait(&data->cond, &data->mutex);
>
> The thread is signalling its own self and presumably locking dead.
>

The intent of this is that the main thread waits for the change of
state to 1 (changed in this thread) and then this thread waits for the
state change to 2 (changed in the main thread).
Won’t that work? Maybe I overlook something.

(The reason for doing this was so that I can test the main thread 
waiting
on condition signaled by another thread and then test that thread 
waiting
on condition signaled by the main thread, so that both variants are 
tested)

>> +    }
>> +    vlc_cleanup_pop();
>> +    vlc_mutex_unlock(&data->mutex);
>> +
>> +    return &thread_return_magic;
>> +}
>> +
>> +static void *thread_func_cond_timeout(void *ptr)
>> +{
>> +    struct cond_data *data = ptr;
>> +
>> +    // Wait until timeout
>> +    vlc_mutex_lock(&data->mutex);
>> +    mutex_cleanup_push(&data->mutex);
>> +
>> +    vlc_tick_t now = vlc_tick_now();
>> +    vlc_tick_t deadline = now + VLC_TICK_FROM_MS(25);
>> +    int ret = vlc_cond_timedwait(&data->cond, &data->mutex, 
>> deadline);
>> +    assert(ret == ETIMEDOUT);
>> +
>> +    vlc_tick_t elapsed = vlc_tick_now() - now;
>> +    assert(elapsed >= VLC_TICK_FROM_MS(25));
>> +
>> +    vlc_cleanup_pop();
>> +    vlc_mutex_unlock(&data->mutex);
>> +
>> +    return &thread_return_magic;
>> +}
>> +
>> +static void *thread_func_tick_sleep(void *data)
>> +{
>> +    assert(data == &thread_data_magic);
>> +    vlc_tick_sleep(VLC_TICK_FROM_SEC(10));
>> +    return &thread_return_magic;
>> +}
>> +
>> +static void *thread_func_cond_multi(void *ptr)
>> +{
>> +    struct cond_data *data = ptr;
>> +
>> +    // Decrement state
>> +    vlc_mutex_lock(&data->mutex);
>> +    data->state -= 1;
>> +    vlc_cond_signal(&data->cond);
>> +    vlc_mutex_unlock(&data->mutex);
>> +
>> +    // Wait for state change
>> +    vlc_mutex_lock(&data->mutex);
>> +    mutex_cleanup_push(&data->mutex);
>> +    while(data->state != -1) {
>> +        vlc_cond_wait(&data->cond, &data->mutex);
>> +    }
>> +    vlc_cleanup_pop();
>> +    vlc_mutex_unlock(&data->mutex);
>> +
>> +    return &thread_return_magic;
>> +}
>> +
>> +
>> +int main(void)
>> +{
>> +    alarm(4);
>> +
>> +    // Test thread creation
>> +    {
>> +        vlc_thread_t th;
>> +        TEST_THREAD_CLONE_MAGIC(&th, thread_func_noop);
>> +        TEST_THREAD_JOIN_MAGIC(th);
>> +    }
>> +
>> +    // Test thread cancellation
>> +    {
>> +        vlc_thread_t th;
>> +        TEST_THREAD_CLONE_MAGIC(&th, thread_func_loop);
>> +        TEST_THREAD_CANCEL_JOIN(th);
>> +    }
>> +
>> +    // Test thread cancellation with cleanup
>> +    {
>> +        vlc_thread_t th;
>> +        int th_cleanup_state = 0;
>> +
>> +        TEST_THREAD_CLONE(&th, thread_func_loop_cleanup,
>> &th_cleanup_state); +        TEST_THREAD_CANCEL_JOIN(th);
>> +        assert(th_cleanup_state == 1);
>> +    }
>> +
>> +    // Test thread waiting on condition
>> +    {
>> +        struct cond_data data;
>> +        data.state = 0;
>> +        vlc_mutex_init(&data.mutex);
>> +        vlc_cond_init(&data.cond);
>> +
>> +        vlc_thread_t th;
>> +        TEST_THREAD_CLONE(&th, thread_func_cond_states, &data);
>> +
>> +        // Wait for state 1
>> +        vlc_mutex_lock(&data.mutex);
>> +        while(data.state != 1) {
>> +            vlc_cond_wait(&data.cond, &data.mutex);
>> +        }
>> +        vlc_mutex_unlock(&data.mutex);
>> +
>> +        // Change to state 2
>> +        vlc_mutex_lock(&data.mutex);
>> +        data.state = 2;
>> +        vlc_cond_signal(&data.cond);
>> +        vlc_mutex_unlock(&data.mutex);
>> +
>> +        // Wait for thread exit
>> +        TEST_THREAD_JOIN_MAGIC(th);
>> +    }
>> +
>> +    // Test thread cancelling/cleanup while waiting on condition
>> +    {
>> +        struct cond_data data;
>> +        data.state = 0;
>> +        vlc_mutex_init(&data.mutex);
>> +        vlc_cond_init(&data.cond);
>> +
>> +        vlc_thread_t th;
>> +        TEST_THREAD_CLONE(&th, thread_func_cond_states, &data);
>> +
>> +        // Wait for state 1
>> +        vlc_mutex_lock(&data.mutex);
>> +        while(data.state != 1) {
>> +            vlc_cond_wait(&data.cond, &data.mutex);
>> +        }
>> +        vlc_mutex_unlock(&data.mutex);
>> +
>> +        // Cancel thread
>> +        TEST_THREAD_CANCEL_JOIN(th);
>> +    }
>> +
>> +    // Wait on condition and let it timeout
>> +    {
>> +        struct cond_data data;
>> +        data.state = 0;
>> +        vlc_mutex_init(&data.mutex);
>> +        vlc_cond_init(&data.cond);
>> +
>> +        vlc_thread_t th;
>> +        TEST_THREAD_CLONE(&th, thread_func_cond_timeout, &data);
>> +
>> +        // Wait for thread exit
>> +        TEST_THREAD_JOIN_MAGIC(th);
>> +    }
>> +
>> +    // Test sleeping for delay
>> +    {
>> +        vlc_tick_t now = vlc_tick_now();
>> +
>> +        vlc_tick_sleep(VLC_TICK_FROM_MS(25));
>> +
>> +        vlc_tick_t elapsed = vlc_tick_now() - now;
>> +        assert(elapsed >= VLC_TICK_FROM_MS(25));
>> +    }
>> +
>> +    // Test sleeping for 0 ticks
>> +    {
>> +        vlc_tick_sleep(0);
>> +    }
>> +
>> +    // Test cancelling vlc_tick_sleep
>> +    {
>> +        vlc_thread_t th;
>> +        TEST_THREAD_CLONE_MAGIC(&th, thread_func_tick_sleep);
>> +        TEST_THREAD_CANCEL_JOIN(th);
>> +    }
>> +
>> +    // Test condition waking several threads
>> +    {
>> +        vlc_thread_t threads[20];
>> +
>> +        struct cond_data data;
>> +        vlc_mutex_init(&data.mutex);
>> +        vlc_cond_init(&data.cond);
>> +        data.state = ARRAY_SIZE(threads);
>> +
>> +        // Spawn all threads
>> +        for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
>> +            TEST_THREAD_CLONE(&threads[i], thread_func_cond_multi, 
>> &data);
>> +        }
>> +
>> +        // Wait for all threads to decrement state
>> +        vlc_mutex_lock(&data.mutex);
>> +        while(data.state > 0) {
>> +            vlc_cond_wait(&data.cond, &data.mutex);
>> +        }
>> +        vlc_mutex_unlock(&data.mutex);
>> +
>> +        // Broadcast condition to all threads
>> +        vlc_mutex_lock(&data.mutex);
>> +        data.state = -1;
>> +        vlc_cond_broadcast(&data.cond);
>> +        vlc_mutex_unlock(&data.mutex);
>> +
>> +        // Wait for all threads to exit
>> +        for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
>> +            TEST_THREAD_JOIN_MAGIC(threads[i]);
>> +        }
>> +    }
>> +
>> +    return 0;
>> +}
>
>
> -- 
> Реми Дёни-Курмон
> http://www.remlab.net/
>
>
>
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel


More information about the vlc-devel mailing list