PR #22733 opened by sohamukute
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22733
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22733.patch
Adds unit tests for three previously uncovered libavutil modules.
executor: Tests av_executor_alloc, av_executor_execute, and av_executor_free in
single-threaded mode (thread_count=0). Covers NULL/bad-callback guards,
priority-ordered draining via a root task that enqueues sub-tasks while
e->recursive is true, and OOM via av_max_alloc(1). Coverage: 0.00% -> 60.82%
(remaining uncovered lines are the HAVE_THREADS paths).
video_hint: Tests av_video_hint_alloc with 0, 1, and 4 rects, rect field
accessors, side data attachment and read-back from AVFrame, overflow guard for
large nb_rects, and OOM paths for both alloc and create_side_data. Coverage:
0.00% -> 85.71%.
ambient_viewing_environment: Tests alloc with and without size output, default
field values (AVRational {0,1}), field round-trip, side data pointer identity
on AVFrame, and OOM paths for both alloc and create_side_data. Coverage: 60.00%
-> 100.00%.
>From 42f6d807e0bd38acb73dd9d370568c8c0e16e7bb Mon Sep 17 00:00:00 2001
From: Soham Kute <[email protected]>
Date: Tue, 7 Apr 2026 01:48:12 +0530
Subject: [PATCH 1/3] libavutil/tests: add FATE test for executor
Unit test covering av_executor_alloc, av_executor_execute, and
av_executor_free using thread_count=0 (synchronous mode).
Tests NULL and bad-callback guard paths, priority-ordered draining by
enqueuing 5 sub-tasks (priorities 3,1,4,1,5) from within a running
task callback while e->recursive==true, single-task smoke test, and
OOM via av_max_alloc(1).
Coverage for libavutil/executor.c: 0.00% -> 60.82%
Signed-off-by: Soham Kute <[email protected]>
---
libavutil/Makefile | 3 +
libavutil/tests/executor.c | 173 +++++++++++++++++++++++++++++++++++++
tests/fate/libavutil.mak | 15 ++++
3 files changed, 191 insertions(+)
create mode 100644 libavutil/tests/executor.c
diff --git a/libavutil/Makefile b/libavutil/Makefile
index c5241895ff..3295c595bd 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -257,6 +257,7 @@ SKIPHEADERS-$(CONFIG_SHADER_COMPRESSION) += zlib_utils.h
TESTPROGS = adler32 \
aes \
+ ambient_viewing_environment \
aes_ctr \
audio_fifo \
avstring \
@@ -275,6 +276,7 @@ TESTPROGS = adler32
\
encryption_info \
error \
eval \
+ executor \
file \
fifo \
hash \
@@ -304,6 +306,7 @@ TESTPROGS = adler32
\
twofish \
utf8 \
uuid \
+ video_hint \
xtea \
tea \
diff --git a/libavutil/tests/executor.c b/libavutil/tests/executor.c
new file mode 100644
index 0000000000..fa6b0e3318
--- /dev/null
+++ b/libavutil/tests/executor.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2026 Soham Kute <[email protected]>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include "libavutil/avassert.h"
+#include "libavutil/executor.c"
+
+#define NUM_TASKS 5
+
+typedef struct TestTask {
+ AVTask base; /* must be first */
+ int id;
+ int priority;
+ int is_root; /* root task enqueues sub-tasks; sub-tasks record order
*/
+} TestTask;
+
+typedef struct TestCtx {
+ AVExecutor *e;
+ TestTask sub_tasks[NUM_TASKS];
+ int exec_log[NUM_TASKS];
+ int exec_idx;
+} TestCtx;
+
+static int task_priority_higher(const AVTask *a, const AVTask *b)
+{
+ return ((const TestTask *)a)->priority > ((const TestTask *)b)->priority;
+}
+
+static int task_ready(const AVTask *t, void *user_data) { return 1; }
+
+/*
+ * A single run callback handles both the root task and sub-tasks.
+ *
+ * Root task (is_root==1): enqueues NUM_TASKS sub-tasks from within this
+ * callback. Because e->recursive is true at this point, each
+ * av_executor_execute call only inserts into the sorted queue and returns
+ * immediately. After root_run returns, the outer drain loop in
+ * av_executor_execute processes the queued sub-tasks in priority order.
+ *
+ * Sub-tasks (is_root==0): record their id in exec_log.
+ */
+static int task_run(AVTask *t, void *lc, void *user_data)
+{
+ TestCtx *ctx = (TestCtx *)user_data;
+ TestTask *tt = (TestTask *)t;
+
+ if (tt->is_root) {
+ /* priorities: 3, 1, 4, 1, 5 - expected drain order: 5,4,3,1,1 */
+ int prios[NUM_TASKS] = {3, 1, 4, 1, 5};
+ for (int i = 0; i < NUM_TASKS; i++) {
+ memset(&ctx->sub_tasks[i], 0, sizeof(ctx->sub_tasks[i]));
+ ctx->sub_tasks[i].id = i;
+ ctx->sub_tasks[i].priority = prios[i];
+ ctx->sub_tasks[i].is_root = 0;
+ av_executor_execute(ctx->e, &ctx->sub_tasks[i].base);
+ }
+ } else {
+ ctx->exec_log[ctx->exec_idx++] = tt->id;
+ }
+ return 0;
+}
+
+int main(void)
+{
+ TestCtx ctx = { 0 };
+ TestTask root = { .base = {0}, .id = -1, .priority = 99, .is_root = 1 };
+
+ AVTaskCallbacks cb = {
+ .user_data = &ctx,
+ .local_context_size = 0,
+ .priority_higher = task_priority_higher,
+ .ready = task_ready,
+ .run = task_run,
+ };
+
+ /* NULL and bad-callback guard paths */
+ av_assert0(!av_executor_alloc(NULL, 0));
+ {
+ AVTaskCallbacks bad = cb;
+ bad.user_data = NULL;
+ av_assert0(!av_executor_alloc(&bad, 0));
+ }
+ {
+ AVTaskCallbacks bad = cb;
+ bad.ready = NULL;
+ av_assert0(!av_executor_alloc(&bad, 0));
+ }
+ {
+ AVTaskCallbacks bad = cb;
+ bad.run = NULL;
+ av_assert0(!av_executor_alloc(&bad, 0));
+ }
+ {
+ AVTaskCallbacks bad = cb;
+ bad.priority_higher = NULL;
+ av_assert0(!av_executor_alloc(&bad, 0));
+ }
+
+ /* av_executor_free on NULL and *NULL is a no-op */
+ av_executor_free(NULL);
+ {
+ AVExecutor *null_e = NULL;
+ av_executor_free(&null_e);
+ }
+
+ /*
+ * Priority ordering in single-threaded mode (thread_count=0).
+ *
+ * The root task enqueues 5 sub-tasks from within task_run. While
+ * e->recursive==true, each av_executor_execute only inserts into the
+ * sorted linked list. After root returns, the outer drain loop runs
+ * sub-tasks in descending priority order: 5, 4, 3, 1, 1.
+ */
+ ctx.e = av_executor_alloc(&cb, 0);
+ av_assert0(ctx.e);
+
+ av_executor_execute(ctx.e, &root.base);
+
+ av_assert0(ctx.exec_idx == NUM_TASKS);
+
+ printf("Execution order (id:priority):");
+ for (int i = 0; i < NUM_TASKS; i++)
+ printf(" %d:%d", ctx.exec_log[i],
ctx.sub_tasks[ctx.exec_log[i]].priority);
+ printf("\n");
+
+ /* Verify descending priority ordering. */
+ for (int i = 0; i + 1 < NUM_TASKS; i++)
+ av_assert0(ctx.sub_tasks[ctx.exec_log[i]].priority >=
+ ctx.sub_tasks[ctx.exec_log[i + 1]].priority);
+
+ av_executor_free(&ctx.e);
+ av_assert0(!ctx.e);
+
+ /* Single-task smoke test */
+ {
+ TestCtx c2 = { 0 };
+ TestTask t1 = { .base = {0}, .id = 0, .priority = 1, .is_root = 0 };
+ AVTaskCallbacks cb2 = cb;
+ cb2.user_data = &c2;
+ c2.e = av_executor_alloc(&cb2, 0);
+ av_assert0(c2.e);
+ av_executor_execute(c2.e, &t1.base);
+ av_assert0(c2.exec_idx == 1 && c2.exec_log[0] == 0);
+ av_executor_free(&c2.e);
+ }
+
+ /* OOM path */
+ av_max_alloc(1);
+ av_assert0(!av_executor_alloc(&cb, 0));
+ av_max_alloc(INT_MAX);
+
+ printf("OK\n");
+ return 0;
+}
diff --git a/tests/fate/libavutil.mak b/tests/fate/libavutil.mak
index 6bf03b2438..457aaabe59 100644
--- a/tests/fate/libavutil.mak
+++ b/tests/fate/libavutil.mak
@@ -1,3 +1,8 @@
+FATE_LIBAVUTIL += fate-ambient_viewing_environment
+fate-ambient_viewing_environment:
libavutil/tests/ambient_viewing_environment$(EXESUF)
+fate-ambient_viewing_environment: CMD = run
libavutil/tests/ambient_viewing_environment$(EXESUF)
+fate-ambient_viewing_environment: CMP = null
+
FATE_LIBAVUTIL += fate-adler32
fate-adler32: libavutil/tests/adler32$(EXESUF)
fate-adler32: CMD = run libavutil/tests/adler32$(EXESUF)
@@ -79,6 +84,11 @@ fate-encryption-info:
libavutil/tests/encryption_info$(EXESUF)
fate-encryption-info: CMD = run libavutil/tests/encryption_info$(EXESUF)
fate-encryption-info: CMP = null
+FATE_LIBAVUTIL += fate-executor
+fate-executor: libavutil/tests/executor$(EXESUF)
+fate-executor: CMD = run libavutil/tests/executor$(EXESUF)
+fate-executor: CMP = null
+
FATE_LIBAVUTIL += fate-eval
fate-eval: libavutil/tests/eval$(EXESUF)
fate-eval: CMD = run libavutil/tests/eval$(EXESUF)
@@ -175,6 +185,11 @@ fate-opt: libavutil/tests/opt$(EXESUF)
fate-opt: CMD = run libavutil/tests/opt$(EXESUF)
FATE_LIBAVUTIL += fate-uuid
+
+FATE_LIBAVUTIL += fate-video_hint
+fate-video_hint: libavutil/tests/video_hint$(EXESUF)
+fate-video_hint: CMD = run libavutil/tests/video_hint$(EXESUF)
+fate-video_hint: CMP = null
fate-uuid: libavutil/tests/uuid$(EXESUF)
fate-uuid: CMD = run libavutil/tests/uuid$(EXESUF)
fate-uuid: CMP = null
--
2.52.0
>From 5831586665f299da3eb755192f335880b164822c Mon Sep 17 00:00:00 2001
From: Soham Kute <[email protected]>
Date: Tue, 7 Apr 2026 01:48:23 +0530
Subject: [PATCH 2/3] libavutil/tests: add FATE test for video_hint
Unit test covering av_video_hint_alloc, av_video_hint_get_rect,
av_video_hint_rects, and av_video_hint_create_side_data.
Tests alloc with 0, 1, and 4 rects; rect field round-trip; side data
attachment and read-back from AVFrame; overflow guard for nb_rects;
and OOM paths for both alloc and create_side_data.
Coverage for libavutil/video_hint.c: 0.00% -> 85.71%
Signed-off-by: Soham Kute <[email protected]>
---
libavutil/tests/video_hint.c | 120 +++++++++++++++++++++++++++++++++++
1 file changed, 120 insertions(+)
create mode 100644 libavutil/tests/video_hint.c
diff --git a/libavutil/tests/video_hint.c b/libavutil/tests/video_hint.c
new file mode 100644
index 0000000000..a05056110e
--- /dev/null
+++ b/libavutil/tests/video_hint.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2026 Soham Kute <[email protected]>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include <stddef.h>
+#include "libavutil/avassert.h"
+#include "libavutil/frame.h"
+#include "libavutil/mem.h"
+#include "libavutil/video_hint.c"
+
+int main(void)
+{
+ size_t size;
+ AVVideoHint *hint;
+ AVVideoRect *rect;
+ AVFrame *frame;
+
+ /* alloc with 0 rects */
+ size = 0;
+ hint = av_video_hint_alloc(0, &size);
+ av_assert0(hint);
+ av_assert0(hint->nb_rects == 0);
+ av_assert0(hint->rect_size == sizeof(AVVideoRect));
+ av_assert0(size > 0);
+ av_free(hint);
+
+ /* alloc with 1 rect, verify rect accessor */
+ size = 0;
+ hint = av_video_hint_alloc(1, &size);
+ av_assert0(hint);
+ av_assert0(hint->nb_rects == 1);
+
+ rect = av_video_hint_get_rect(hint, 0);
+ av_assert0(rect);
+ rect->x = 10;
+ rect->y = 20;
+ rect->width = 100;
+ rect->height = 200;
+
+ av_assert0(av_video_hint_rects(hint)->x == 10);
+ av_assert0(av_video_hint_rects(hint)->y == 20);
+
+ hint->type = AV_VIDEO_HINT_TYPE_CHANGED;
+ printf("1-rect hint: nb_rects=%zu type=%d x=%u y=%u w=%u h=%u\n",
+ hint->nb_rects, hint->type,
+ rect->x, rect->y, rect->width, rect->height);
+ av_free(hint);
+
+ /* alloc with 4 rects */
+ size = 0;
+ hint = av_video_hint_alloc(4, &size);
+ av_assert0(hint);
+ av_assert0(hint->nb_rects == 4);
+ for (size_t i = 0; i < 4; i++) {
+ rect = av_video_hint_get_rect(hint, i);
+ rect->x = rect->y = (uint32_t)i * 16;
+ rect->width = rect->height = 16;
+ }
+ av_free(hint);
+
+ /* create_side_data attaches hint to frame */
+ frame = av_frame_alloc();
+ av_assert0(frame);
+
+ hint = av_video_hint_create_side_data(frame, 2);
+ av_assert0(hint);
+ av_assert0(hint->nb_rects == 2);
+
+ {
+ const AVFrameSideData *sd =
+ av_frame_get_side_data(frame, AV_FRAME_DATA_VIDEO_HINT);
+ av_assert0(sd);
+ av_assert0((AVVideoHint *)sd->data == hint);
+ }
+ av_frame_free(&frame);
+
+ /* overflow guard: nb_rects too large returns NULL */
+ size = 0;
+ hint = av_video_hint_alloc(SIZE_MAX / sizeof(AVVideoRect) + 1, &size);
+ av_assert0(!hint);
+ av_assert0(size == 0);
+
+ /* OOM path: av_video_hint_alloc fails */
+ av_max_alloc(1);
+ size = 0;
+ hint = av_video_hint_alloc(1, &size);
+ av_assert0(!hint);
+ av_assert0(size == 0);
+ av_max_alloc(INT_MAX);
+
+ /* OOM path: create_side_data fails when hint alloc fails */
+ frame = av_frame_alloc();
+ av_assert0(frame);
+ av_max_alloc(1);
+ hint = av_video_hint_create_side_data(frame, 1);
+ av_assert0(!hint);
+ av_max_alloc(INT_MAX);
+ av_frame_free(&frame);
+
+ printf("OK\n");
+ return 0;
+}
--
2.52.0
>From 008cd4821a7a8e14f5a40d4604b5c9e9c0ad1010 Mon Sep 17 00:00:00 2001
From: Soham Kute <[email protected]>
Date: Tue, 7 Apr 2026 01:48:48 +0530
Subject: [PATCH 3/3] libavutil/tests: add FATE test for
ambient_viewing_environment
Unit test covering av_ambient_viewing_environment_alloc (with and
without size output) and av_ambient_viewing_environment_create_side_data.
Verifies default field values (AVRational {0,1}), field round-trip,
side data attachment and pointer identity check on AVFrame, and OOM
paths for both alloc and create_side_data.
Coverage for libavutil/ambient_viewing_environment.c: 60.00% -> 100.00%
Signed-off-by: Soham Kute <[email protected]>
---
libavutil/tests/ambient_viewing_environment.c | 94 +++++++++++++++++++
1 file changed, 94 insertions(+)
create mode 100644 libavutil/tests/ambient_viewing_environment.c
diff --git a/libavutil/tests/ambient_viewing_environment.c
b/libavutil/tests/ambient_viewing_environment.c
new file mode 100644
index 0000000000..c036ffc7b9
--- /dev/null
+++ b/libavutil/tests/ambient_viewing_environment.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2026 Soham Kute <[email protected]>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include "libavutil/ambient_viewing_environment.c"
+#include "libavutil/avassert.h"
+#include "libavutil/frame.h"
+#include "libavutil/mem.h"
+#include "libavutil/rational.h"
+
+int main(void)
+{
+ AVAmbientViewingEnvironment *env;
+ size_t size = 0;
+ AVFrame *frame;
+
+ /* alloc without size output */
+ env = av_ambient_viewing_environment_alloc(NULL);
+ av_assert0(env);
+ /* defaults: all fields are AVRational {0,1} */
+ av_assert0(env->ambient_illuminance.num == 0 &&
env->ambient_illuminance.den == 1);
+ av_assert0(env->ambient_light_x.num == 0 && env->ambient_light_x.den
== 1);
+ av_assert0(env->ambient_light_y.num == 0 && env->ambient_light_y.den
== 1);
+ av_free(env);
+
+ /* alloc with size output */
+ env = av_ambient_viewing_environment_alloc(&size);
+ av_assert0(env);
+ av_assert0(size == sizeof(AVAmbientViewingEnvironment));
+
+ env->ambient_illuminance = (AVRational){ 1000, 1 };
+ env->ambient_light_x = (AVRational){ 3127, 10000 };
+ env->ambient_light_y = (AVRational){ 3290, 10000 };
+
+ printf("alloc: illuminance=%d/%d x=%d/%d y=%d/%d size=%zu\n",
+ env->ambient_illuminance.num, env->ambient_illuminance.den,
+ env->ambient_light_x.num, env->ambient_light_x.den,
+ env->ambient_light_y.num, env->ambient_light_y.den,
+ size);
+ av_free(env);
+
+ /* create_side_data attaches struct to frame */
+ frame = av_frame_alloc();
+ av_assert0(frame);
+
+ env = av_ambient_viewing_environment_create_side_data(frame);
+ av_assert0(env);
+ av_assert0(env->ambient_illuminance.num == 0 &&
env->ambient_illuminance.den == 1);
+
+ {
+ const AVFrameSideData *sd =
+ av_frame_get_side_data(frame,
AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT);
+ av_assert0(sd);
+ av_assert0((AVAmbientViewingEnvironment *)sd->data == env);
+ printf("side_data: size=%zu\n", sd->size);
+ }
+ av_frame_free(&frame);
+
+ /* OOM path: alloc fails */
+ av_max_alloc(1);
+ env = av_ambient_viewing_environment_alloc(NULL);
+ av_assert0(!env);
+ av_max_alloc(INT_MAX);
+
+ /* OOM path: create_side_data fails when side_data attachment fails */
+ frame = av_frame_alloc();
+ av_assert0(frame);
+ av_max_alloc(1);
+ env = av_ambient_viewing_environment_create_side_data(frame);
+ av_assert0(!env);
+ av_max_alloc(INT_MAX);
+ av_frame_free(&frame);
+
+ printf("OK\n");
+ return 0;
+}
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]