[vlc-devel] [PATCH 2/6] executor: add unit tests
Steve Lhomme
robux4 at ycbcr.xyz
Tue Sep 8 09:39:10 CEST 2020
On 2020-09-07 17:40, Romain Vimont wrote:
> ...
> +static void test_multiple_runnables(void)
> +{
> + vlc_executor_t *executor = vlc_executor_New(3);
> + assert(executor);
> +
> + struct data shared_data;
> + InitData(&shared_data);
> +
> + struct vlc_runnable runnables[300];
This is very inconvenient that you have to allocate and fill 300 times
the same structure exactly the same way. A single instance that you
submit 300 would be better.
> + for (int i = 0; i < 300; ++i)
> + {
> + struct vlc_runnable *runnable = &runnables[i];
> + runnable->run = RunIncrement;
> + runnable->userdata = &shared_data;
> + vlc_executor_Submit(executor, runnable);
> + }
> +
> + vlc_mutex_lock(&shared_data.lock);
> + while (shared_data.ended < 300)
> + vlc_cond_wait(&shared_data.cond, &shared_data.lock);
> + vlc_mutex_unlock(&shared_data.lock);
Since the runnables are in the stack, they could keep track whether they
are finished running or not, and be signaled by the executor when they
are. But I understand that means adding more things to the runnable even
when the feature is not used.
> +
> + assert(shared_data.ended == 300);
> +
> + vlc_executor_Delete(executor);
> +}
> +
> +static void test_blocking_delete(void)
> +{
> + vlc_executor_t *executor = vlc_executor_New(1);
> + assert(executor);
> +
> + struct data data;
> + InitData(&data);
> +
> + data.delay = VLC_TICK_FROM_MS(100);
> +
> + struct vlc_runnable runnable = {
> + .run = RunIncrement,
> + .userdata = &data,
> + };
Usually I prefer to initialize without the field names. That makes
refactoring safer as you can:
- change the field name without changing all user code
- add callbacks and be warned (or fail to compile) if a user is missing
an implementation (or NULL)
> +
> + vlc_executor_Submit(executor, &runnable);
> +
> + /* Wait for the runnable to be started */
> + vlc_mutex_lock(&data.lock);
> + while (data.started == 0)
> + vlc_cond_wait(&data.cond, &data.lock);
> + vlc_mutex_unlock(&data.lock);
> +
> + /* The runnable sleeps for about 100ms */
You may make it longer than 100ms as the test sometimes run in parallel
with other things on busy systems.
> +
> + vlc_executor_Delete(executor);
> +
> + vlc_mutex_lock(&data.lock);
> + /* The executor must wait the end of running tasks on delete */
> + assert(data.ended == 1);
> + vlc_mutex_unlock(&data.lock);
> +}
> +
> +static void test_cancel(void)
> +{
> + vlc_executor_t *executor = vlc_executor_New(4);
> + assert(executor);
> +
> + struct data shared_data;
> + InitData(&shared_data);
> +
> + shared_data.delay = VLC_TICK_FROM_MS(100);
> +
> + /* Submit 40 tasks taking at least 100ms on an executor with at most 4
> + * threads */
> +
> + struct vlc_runnable runnables[40];
> + for (int i = 0; i < 40; ++i)
> + {
> + struct vlc_runnable *runnable = &runnables[i];
> + runnable->run = RunIncrement;
> + runnable->userdata = &shared_data;
> + vlc_executor_Submit(executor, runnable);
> + }
> +
> + /* Wait a bit (in two lines to avoid harmful_delay() warning) */
> + vlc_tick_t delay = VLC_TICK_FROM_MS(150);
> + vlc_tick_sleep(delay);
> +
> + int canceled = 0;
> + for (int i = 0; i < 40; ++i)
> + {
> + if (vlc_executor_Cancel(executor, &runnables[i]))
> + ++canceled;
> + }
> +
> + vlc_mutex_lock(&shared_data.lock);
> + /* All started must not be canceled (but some non-started-yet may not be
> + * canceled either) */
> + assert(canceled + shared_data.started <= 40);
> + vlc_mutex_unlock(&shared_data.lock);
> +
> + vlc_executor_Delete(executor);
> +
> + /* Every started task must also be ended */
> + assert(shared_data.started == shared_data.ended);
> + /* Every task is either canceled or ended */
> + assert(canceled + shared_data.ended == 40);
> +}
> +
> +struct doubler_task
> +{
> + vlc_executor_t *executor;
> + int *array;
> + size_t count;
> + struct vlc_runnable runnable;
> +};
> +
> +static void DoublerRun(void *);
> +
> +static bool
> +SpawnDoublerTask(vlc_executor_t *executor, int *array, size_t count)
> +{
> + struct doubler_task *task = malloc(sizeof(*task));
> + if (!task)
> + return false;
> +
> + task->executor = executor;
> + task->array = array;
> + task->count = count;
> + task->runnable.run = DoublerRun;
> + task->runnable.userdata = task;
> +
> + vlc_executor_Submit(executor, &task->runnable);
> +
> + return true;
> +}
> +
> +static void DoublerRun(void *userdata)
> +{
> + struct doubler_task *task = userdata;
> +
> + if (task->count == 1)
> + task->array[0] *= 2; /* double the value */
> + else
> + {
> + /* Spawn tasks doubling halves of the array recursively */
> + bool ok;
> +
> + ok = SpawnDoublerTask(task->executor, task->array, task->count / 2);
> + assert(ok);
> +
> + ok = SpawnDoublerTask(task->executor, task->array + task->count / 2,
> + task->count - task->count / 2);
> + assert(ok);
> + }
> +
> + free(task);
> +}
> +
> +static void test_task_chain(void)
> +{
> + vlc_executor_t *executor = vlc_executor_New(4);
> + assert(executor);
> +
> + /* Numbers from 0 to 99 */
> + int array[100];
> + for (int i = 0; i < 100; ++i)
> + array[i] = i;
> +
> + /* Double all values in the array from tasks spawning smaller tasks
> + * recursively, until the array has size 1, where the single value is
> + * doubled */
> + SpawnDoublerTask(executor, array, 100);
> +
> + vlc_executor_WaitIdle(executor);
> + vlc_executor_Delete(executor);
> +
> + /* All values must have been doubled */
> + for (int i = 0; i < 100; ++i)
> + assert(array[i] == 2 * i);
> +}
> +
> +int main(void)
> +{
> + test_single_runnable();
> + test_multiple_runnables();
> + test_blocking_delete();
> + test_cancel();
> + test_task_chain();
> + return 0;
> +}
> --
> 2.28.0
>
> _______________________________________________
> 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