Skip to content

Commit bcf1d8f

Browse files
committed
[UT][WATERMARK] add gvawatermark caps negotiation and error path tests
Add GStreamer Check tests using data-driven loop tests for caps negotiation (BGR, NV12, BGRA, RGBA, BGRx, I420), buffer passthrough identity (no metadata = no pixel change), resolution variants (64x48, 1920x1080), and error paths (GPU+system-memory incompatibility, unsupported device name "FPGA"). Not tested: VAAPI/DMA/D3D11 memory caps (require VA hardware), rejected pixel formats, caps renegotiation mid-stream. Signed-off-by: Walid <walid.aly@intel.com>
1 parent 4378aa8 commit bcf1d8f

File tree

2 files changed

+273
-0
lines changed

2 files changed

+273
-0
lines changed

tests/unit_tests/check/elements/watermark/test_watermark/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ set(TARGET_CONFIG "test_gvawatermark_config")
1111
add_executable(${TARGET_CONFIG} ${CMAKE_CURRENT_SOURCE_DIR}/test_watermark_config.cpp)
1212
target_link_libraries(${TARGET_CONFIG} PRIVATE ${COMMON_LIBS})
1313
add_test(NAME ${TARGET_CONFIG} COMMAND ${TARGET_CONFIG} WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
14+
15+
# --- Caps negotiation tests ---
16+
set(TARGET_CAPS "test_gvawatermark_caps")
17+
add_executable(${TARGET_CAPS} ${CMAKE_CURRENT_SOURCE_DIR}/test_watermark_caps.cpp)
18+
target_link_libraries(${TARGET_CAPS} PRIVATE ${COMMON_LIBS})
19+
add_test(NAME ${TARGET_CAPS} COMMAND ${TARGET_CAPS} WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/*******************************************************************************
2+
* Copyright (C) 2026 Intel Corporation
3+
*
4+
* SPDX-License-Identifier: MIT
5+
******************************************************************************/
6+
7+
#include "test_common.h"
8+
#include "test_utils.h"
9+
#include <cstring>
10+
11+
/* ---------- element name under test ---------- */
12+
constexpr char impl_name[] = "gvawatermarkimpl";
13+
14+
static Resolution test_resolution = {320, 240};
15+
16+
/* ========================================================================= */
17+
/* Data-driven caps negotiation */
18+
/* */
19+
/* A single test function iterates over all supported system-memory formats */
20+
/* via tcase_add_loop_test, avoiding duplicated pad template declarations. */
21+
/* ========================================================================= */
22+
23+
struct FormatEntry {
24+
const char *name; /* human-readable, for messages */
25+
const char *caps_string; /* GST_VIDEO_CAPS_MAKE result */
26+
};
27+
28+
/* Constructed at file scope because GST_VIDEO_CAPS_MAKE is a macro that
29+
expands to a string literal — safe for static init. */
30+
static const FormatEntry supported_formats[] = {
31+
{"BGR", GST_VIDEO_CAPS_MAKE("BGR")}, {"NV12", GST_VIDEO_CAPS_MAKE("NV12")}, {"BGRA", GST_VIDEO_CAPS_MAKE("BGRA")},
32+
{"RGBA", GST_VIDEO_CAPS_MAKE("RGBA")}, {"BGRx", GST_VIDEO_CAPS_MAKE("BGRx")}, {"I420", GST_VIDEO_CAPS_MAKE("I420")},
33+
};
34+
static const int NUM_FORMATS = sizeof(supported_formats) / sizeof(supported_formats[0]);
35+
36+
/*
37+
* Build a src/sink GstStaticPadTemplate pair matching the given caps string.
38+
* The caps_string must be a string literal or static storage (GstStaticPadTemplate
39+
* stores the pointer, not a copy).
40+
*/
41+
static GstStaticPadTemplate make_src_template(const char *caps_str) {
42+
GstStaticPadTemplate t = GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS(caps_str));
43+
return t;
44+
}
45+
static GstStaticPadTemplate make_sink_template(const char *caps_str) {
46+
GstStaticPadTemplate t = GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS(caps_str));
47+
return t;
48+
}
49+
50+
GST_START_TEST(test_caps_format_accepted) {
51+
const FormatEntry &fmt = supported_formats[__i__];
52+
g_print("Starting test: test_caps_format_accepted[%s]\n", fmt.name);
53+
54+
GstStaticPadTemplate src = make_src_template(fmt.caps_string);
55+
GstStaticPadTemplate sink = make_sink_template(fmt.caps_string);
56+
57+
run_test(impl_name, fmt.caps_string, test_resolution, &src, &sink, NULL, NULL, NULL, NULL);
58+
}
59+
GST_END_TEST;
60+
61+
/* ========================================================================= */
62+
/* GPU device with system memory should fail */
63+
/* ========================================================================= */
64+
65+
#define WATERMARK_BGR_CAPS GST_VIDEO_CAPS_MAKE("BGR")
66+
67+
GST_START_TEST(test_gpu_device_with_system_memory_fails) {
68+
g_print("Starting test: test_gpu_device_with_system_memory_fails\n");
69+
70+
GstElement *pipeline = gst_pipeline_new("test-pipeline");
71+
GstElement *source = gst_element_factory_make("videotestsrc", "source");
72+
GstElement *element = gst_element_factory_make(impl_name, "watermark");
73+
GstElement *sink = gst_element_factory_make("fakesink", "sink");
74+
75+
ck_assert(pipeline && source && element && sink);
76+
77+
g_object_set(G_OBJECT(source), "num-buffers", 1, NULL);
78+
g_object_set(G_OBJECT(element), "device", "GPU", NULL);
79+
80+
gst_bin_add_many(GST_BIN(pipeline), source, element, sink, NULL);
81+
gst_element_link_many(source, element, sink, NULL);
82+
83+
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
84+
gst_element_set_state(pipeline, GST_STATE_PLAYING);
85+
86+
GstMessage *msg = gst_bus_poll(bus, (GstMessageType)(GST_MESSAGE_ERROR | GST_MESSAGE_EOS), 5 * GST_SECOND);
87+
ck_assert_msg(msg != NULL, "Expected an error message on the bus");
88+
ck_assert_msg(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR, "Expected ERROR message, got %s",
89+
GST_MESSAGE_TYPE_NAME(msg));
90+
91+
GError *err = NULL;
92+
gst_message_parse_error(msg, &err, NULL);
93+
ck_assert(err != NULL);
94+
ck_assert_msg(strstr(err->message, "incompatible with System Memory") != NULL,
95+
"Error message does not mention 'incompatible with System Memory'. Got: %s", err->message);
96+
97+
g_error_free(err);
98+
gst_message_unref(msg);
99+
gst_object_unref(bus);
100+
gst_element_set_state(pipeline, GST_STATE_NULL);
101+
gst_object_unref(pipeline);
102+
}
103+
GST_END_TEST;
104+
105+
/* ========================================================================= */
106+
/* Buffer pass-through with no metadata */
107+
/* */
108+
/* Push a buffer filled with 0xAB and no ROI metadata through the element. */
109+
/* Verify the output buffer is byte-identical (in-place, no rendering). */
110+
/* ========================================================================= */
111+
112+
static void fill_buffer_with_pattern(GstBuffer *buffer, gpointer user_data) {
113+
(void)user_data;
114+
GstMapInfo info;
115+
ck_assert(gst_buffer_map(buffer, &info, GST_MAP_WRITE));
116+
memset(info.data, 0xAB, info.size);
117+
gst_buffer_unmap(buffer, &info);
118+
}
119+
120+
static void verify_buffer_unchanged(GstBuffer *buffer, gpointer user_data) {
121+
(void)user_data;
122+
GstMapInfo info;
123+
ck_assert(gst_buffer_map(buffer, &info, GST_MAP_READ));
124+
125+
/* Fast path: single memcmp against a reference pattern */
126+
guint8 *reference = (guint8 *)g_malloc(info.size);
127+
memset(reference, 0xAB, info.size);
128+
int cmp = memcmp(info.data, reference, info.size);
129+
if (cmp != 0) {
130+
/* Find first mismatch for a useful diagnostic */
131+
for (gsize i = 0; i < info.size; i++) {
132+
if (info.data[i] != 0xAB) {
133+
g_free(reference);
134+
gst_buffer_unmap(buffer, &info);
135+
ck_abort_msg("Buffer byte %zu was modified (expected 0xAB, got 0x%02X). "
136+
"No metadata was attached so watermark should not modify pixels.",
137+
i, info.data[i]);
138+
}
139+
}
140+
}
141+
g_free(reference);
142+
gst_buffer_unmap(buffer, &info);
143+
}
144+
145+
/*
146+
* Passthrough tests use a loop over a subset of formats (BGR + NV12)
147+
* to verify in-place identity for both packed and planar layouts.
148+
*/
149+
static const FormatEntry passthrough_formats[] = {
150+
{"BGR", GST_VIDEO_CAPS_MAKE("BGR")},
151+
{"NV12", GST_VIDEO_CAPS_MAKE("NV12")},
152+
};
153+
static const int NUM_PASSTHROUGH_FORMATS = sizeof(passthrough_formats) / sizeof(passthrough_formats[0]);
154+
155+
GST_START_TEST(test_passthrough_no_metadata) {
156+
const FormatEntry &fmt = passthrough_formats[__i__];
157+
g_print("Starting test: test_passthrough_no_metadata[%s]\n", fmt.name);
158+
159+
GstStaticPadTemplate src = make_src_template(fmt.caps_string);
160+
GstStaticPadTemplate sink = make_sink_template(fmt.caps_string);
161+
162+
run_test(impl_name, fmt.caps_string, test_resolution, &src, &sink, fill_buffer_with_pattern,
163+
verify_buffer_unchanged, NULL, NULL);
164+
}
165+
GST_END_TEST;
166+
167+
/* ========================================================================= */
168+
/* Unsupported device name should fail */
169+
/* ========================================================================= */
170+
171+
GST_START_TEST(test_unsupported_device_name_fails) {
172+
g_print("Starting test: test_unsupported_device_name_fails\n");
173+
174+
GstElement *pipeline = gst_pipeline_new("test-pipeline");
175+
GstElement *source = gst_element_factory_make("videotestsrc", "source");
176+
GstElement *element = gst_element_factory_make(impl_name, "watermark");
177+
GstElement *sink = gst_element_factory_make("fakesink", "sink");
178+
179+
ck_assert(pipeline && source && element && sink);
180+
181+
g_object_set(G_OBJECT(source), "num-buffers", 1, NULL);
182+
g_object_set(G_OBJECT(element), "device", "FPGA", NULL);
183+
184+
gst_bin_add_many(GST_BIN(pipeline), source, element, sink, NULL);
185+
gst_element_link_many(source, element, sink, NULL);
186+
187+
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
188+
gst_element_set_state(pipeline, GST_STATE_PLAYING);
189+
190+
GstMessage *msg = gst_bus_poll(bus, (GstMessageType)(GST_MESSAGE_ERROR | GST_MESSAGE_EOS), 5 * GST_SECOND);
191+
ck_assert_msg(msg != NULL, "Expected an error message on the bus");
192+
ck_assert_msg(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR, "Expected ERROR message, got %s",
193+
GST_MESSAGE_TYPE_NAME(msg));
194+
195+
GError *err = NULL;
196+
gchar *dbg = NULL;
197+
gst_message_parse_error(msg, &err, &dbg);
198+
ck_assert(err != NULL);
199+
ck_assert_msg(strstr(err->message, "Unsupported") != NULL || (dbg && strstr(dbg, "not supported") != NULL),
200+
"Error should mention unsupported device. Got: %s (debug: %s)", err->message, dbg ? dbg : "(null)");
201+
202+
g_error_free(err);
203+
g_free(dbg);
204+
gst_message_unref(msg);
205+
gst_object_unref(bus);
206+
gst_element_set_state(pipeline, GST_STATE_NULL);
207+
gst_object_unref(pipeline);
208+
}
209+
GST_END_TEST;
210+
211+
/* ========================================================================= */
212+
/* Various resolutions */
213+
/* ========================================================================= */
214+
215+
static const Resolution resolution_variants[] = {
216+
{64, 48},
217+
{1920, 1080},
218+
};
219+
static const int NUM_RESOLUTIONS = sizeof(resolution_variants) / sizeof(resolution_variants[0]);
220+
221+
GST_START_TEST(test_caps_resolution_variant) {
222+
const Resolution &res = resolution_variants[__i__];
223+
g_print("Starting test: test_caps_resolution_variant[%dx%d]\n", res.width, res.height);
224+
225+
GstStaticPadTemplate src = make_src_template(GST_VIDEO_CAPS_MAKE("BGR"));
226+
GstStaticPadTemplate sink = make_sink_template(GST_VIDEO_CAPS_MAKE("BGR"));
227+
228+
run_test(impl_name, GST_VIDEO_CAPS_MAKE("BGR"), res, &src, &sink, NULL, NULL, NULL, NULL);
229+
}
230+
GST_END_TEST;
231+
232+
/* ========================================================================= */
233+
/* Suite setup */
234+
/* ========================================================================= */
235+
236+
static Suite *watermark_caps_testing_suite(void) {
237+
Suite *s = suite_create("watermark_caps_testing");
238+
239+
/* Caps negotiation — each supported system memory format (loop test) */
240+
TCase *tc_caps = tcase_create("caps_negotiation");
241+
tcase_set_timeout(tc_caps, 30);
242+
suite_add_tcase(s, tc_caps);
243+
tcase_add_loop_test(tc_caps, test_caps_format_accepted, 0, NUM_FORMATS);
244+
245+
/* Failure cases */
246+
TCase *tc_fail = tcase_create("caps_failure");
247+
tcase_set_timeout(tc_fail, 30);
248+
suite_add_tcase(s, tc_fail);
249+
tcase_add_test(tc_fail, test_gpu_device_with_system_memory_fails);
250+
tcase_add_test(tc_fail, test_unsupported_device_name_fails);
251+
252+
/* Buffer pass-through with no metadata (loop test) */
253+
TCase *tc_passthrough = tcase_create("buffer_passthrough");
254+
tcase_set_timeout(tc_passthrough, 30);
255+
suite_add_tcase(s, tc_passthrough);
256+
tcase_add_loop_test(tc_passthrough, test_passthrough_no_metadata, 0, NUM_PASSTHROUGH_FORMATS);
257+
258+
/* Resolution variants (loop test) */
259+
TCase *tc_resolution = tcase_create("resolution_variants");
260+
tcase_set_timeout(tc_resolution, 30);
261+
suite_add_tcase(s, tc_resolution);
262+
tcase_add_loop_test(tc_resolution, test_caps_resolution_variant, 0, NUM_RESOLUTIONS);
263+
264+
return s;
265+
}
266+
267+
GST_CHECK_MAIN(watermark_caps_testing);

0 commit comments

Comments
 (0)