|
| 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