dispserv_x11: regression tests for the XRandR failure paths

Covers the crash that took RetroArch down inside
video_display_server_init(), before anything was drawn, on any X server
advertising RandR without a usable output:

  #0 x11_display_server_get_screen_orientation ()
  #1 video_display_server_init ()
  #2 rarch_main ()

and the crtc mis-indexing in the same walks.

The tests run against a stub X server rather than a real one.  That is
the point rather than a shortcut: what is under test is the behaviour
when the RandR queries *fail*, and a working display is precisely the
environment that cannot produce it.  xrandr_stub.c stands in for
libXrandr and the handful of Xlib entry points dispserv_x11.c reaches,
so no X server, no display and no RandR extension are needed to run
them.

Two things the stub does beyond returning NULL on demand.  It numbers
the screen's crtcs from 100 and the output's from 200, so which array
the code walked is readable straight off the call log rather than
inferred.  And it tracks every pointer it hands out, so a double free,
a free of something it never allocated, or a leak is an assertion
failure in the test that caused it, instead of something the sanitizer
reports against whichever test happens to run last.  Three of the leaks
fixed in the previous commit were found that way.

dispserv_x11.c refers to only four RetroArch-side symbols -- g_x11_dpy,
g_x11_win, strlcpy_retro__ and video_monitor_set_refresh_rate -- which
is what makes it linkable in isolation at all.  stubs_retroarch.c
supplies them, and is short enough that it is worth noticing if it
grows.

Eleven tests.  Against this tree they pass.  Against 26b640f^, the
tree before any of this was fixed, eight fail: six segfaults and two
assertions, including the crtc index one reporting 100 where it should
be 200.

Build and run with `make` in gfx/display_servers/test.  Needs libcheck
and the X11/Xrandr headers; flags follow libretro-common/Makefile.test,
including ASan and UBSan.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-07-28 22:11:40 +02:00
1 parent 39410b786a
commit d40259078d
6 files changed
+909

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
test_dispserv_x11
*.gcda
*.gcno
+42
View File
@@ -0,0 +1,42 @@
# Regression tests for gfx/display_servers/dispserv_x11.c.
#
# Run from this directory:
#
# make build and run
# make clean
#
# Needs libcheck and the X11/Xrandr headers. It does *not* need an X
# server, a display or the RandR extension: xrandr_stub.c stands in for
# libXrandr and the handful of Xlib entry points dispserv_x11.c reaches,
# which is the only way to drive the failure modes under test. Flags
# follow libretro-common/Makefile.test.
ROOT = ../../..
CC ?= gcc
TEST_UNIT_CFLAGS = $(CFLAGS) -I. -I$(ROOT) -I$(ROOT)/libretro-common/include \
-DHAVE_XRANDR \
$(shell pkg-config --cflags check) \
-Wall -Wdeclaration-after-statement \
-fsanitize=address -fsanitize=undefined -ggdb
TEST_LIBS = $(shell pkg-config --libs check)
TEST_DISPSERV_X11 = test_dispserv_x11
TEST_DISPSERV_X11_SRC = test_dispserv_x11.c xrandr_stub.c stubs_retroarch.c \
$(ROOT)/gfx/display_servers/dispserv_x11.c
all: run
$(TEST_DISPSERV_X11): $(TEST_DISPSERV_X11_SRC)
$(CC) $(TEST_UNIT_CFLAGS) $(TEST_DISPSERV_X11_SRC) $(TEST_LIBS) \
-o $(TEST_DISPSERV_X11)
run: $(TEST_DISPSERV_X11)
./$(TEST_DISPSERV_X11)
clean:
rm -f $(TEST_DISPSERV_X11) *.gcda *.gcno
.PHONY: all run clean
@@ -0,0 +1,34 @@
/* Copyright (C) 2010-2025 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (stubs_retroarch.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* The only RetroArch-side symbols dispserv_x11.c refers to. Keeping
* the list this short is what makes the file testable in isolation; if
* it grows, that is worth noticing. */
#include <string.h>
#include <stddef.h>
#include <X11/Xlib.h>
Display *g_x11_dpy = NULL;
Window g_x11_win = 0;
size_t strlcpy_retro__(char *d, const char *s, size_t n)
{ size_t sl = strlen(s); if (n) { size_t c = sl < n-1 ? sl : n-1; memcpy(d,s,c); d[c]='\0'; } return sl; }
void video_monitor_set_refresh_rate(float hz) { (void)hz; }
@@ -0,0 +1,367 @@
/* Copyright (C) 2010-2025 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (test_dispserv_x11.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* Regression tests for the XRandR query handling in
* gfx/display_servers/dispserv_x11.c.
*
* These cover a crash that took RetroArch down inside
* video_display_server_init(), before anything was drawn, on any X
* server that advertises RandR without a usable output:
*
* #0 x11_display_server_get_screen_orientation ()
* #1 video_display_server_init ()
* #2 rarch_main ()
*
* and an out-of-bounds read in the same walks, which indexed the
* screen's crtc array with the output's crtc count.
*
* The tests run against a stub X server (xrandr_stub.c) rather than a
* real one, because the behaviour under test is what happens when the
* RandR queries *fail*, and a working display is exactly the
* environment that cannot produce that. The stub also numbers the
* screen's crtcs and the output's crtcs from different bases, so which
* array the code walked is readable straight off the call log.
*/
#include <check.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <X11/extensions/Xrandr.h>
#include "../../video_display_server.h"
#include "xrandr_stub.h"
#define SUITE_NAME "dispserv_x11"
extern const video_display_server_t dispserv_x11;
/* Defaults: everything succeeds, one connected output, screen and
* output agreeing on a single crtc. */
static void cfg_default(xrandr_stub_cfg_t *cfg)
{
memset(cfg, 0, sizeof(*cfg));
cfg->screen_ncrtc = 1;
cfg->output_ncrtc = 1;
cfg->output_connection = RR_Connected;
}
static void *serv_init(void)
{
return dispserv_x11.init();
}
static void serv_free(void *data)
{
if (data)
dispserv_x11.destroy(data);
}
/* --- the crash --- */
START_TEST (test_orientation_output_info_null)
{
xrandr_stub_cfg_t cfg;
void *data;
cfg_default(&cfg);
cfg.fail_output_info = 1;
xrandr_stub_reset(&cfg);
data = serv_init();
/* Pre-fix this dereferenced NULL->connection and took the process
* with it. */
ck_assert_int_eq((int)dispserv_x11.get_screen_orientation(data),
(int)ORIENTATION_NORMAL);
serv_free(data);
ck_assert_int_eq(xrandr_stub_log()->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
START_TEST (test_orientation_crtc_info_null)
{
xrandr_stub_cfg_t cfg;
void *data;
cfg_default(&cfg);
cfg.fail_crtc_info = 1;
xrandr_stub_reset(&cfg);
data = serv_init();
/* Pre-fix this dereferenced NULL->width. */
ck_assert_int_eq((int)dispserv_x11.get_screen_orientation(data),
(int)ORIENTATION_NORMAL);
serv_free(data);
ck_assert_int_eq(xrandr_stub_log()->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
START_TEST (test_orientation_screen_resources_null)
{
xrandr_stub_cfg_t cfg;
void *data;
cfg_default(&cfg);
cfg.fail_screen_resources = 1;
xrandr_stub_reset(&cfg);
data = serv_init();
ck_assert_int_eq((int)dispserv_x11.get_screen_orientation(data),
(int)ORIENTATION_NORMAL);
serv_free(data);
ck_assert_int_eq(xrandr_stub_log()->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
START_TEST (test_orientation_screen_info_null)
{
xrandr_stub_cfg_t cfg;
void *data;
cfg_default(&cfg);
cfg.fail_screen_info = 1;
xrandr_stub_reset(&cfg);
data = serv_init();
ck_assert_int_eq((int)dispserv_x11.get_screen_orientation(data),
(int)ORIENTATION_NORMAL);
serv_free(data);
/* XRRFreeScreenConfigInfo() is not NULL-tolerant; the cleanup path
* has to test before freeing. The stub counts a NULL free as a bad
* one rather than quietly accepting it. */
ck_assert_int_eq(xrandr_stub_log()->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
/* A disconnected output is the ordinary shape of "RandR present, no
* usable output" and must simply yield the default orientation. */
START_TEST (test_orientation_output_disconnected)
{
xrandr_stub_cfg_t cfg;
void *data;
cfg_default(&cfg);
cfg.output_connection = RR_Disconnected;
xrandr_stub_reset(&cfg);
data = serv_init();
ck_assert_int_eq((int)dispserv_x11.get_screen_orientation(data),
(int)ORIENTATION_NORMAL);
serv_free(data);
ck_assert_int_eq(xrandr_stub_log()->get_crtc_info_calls, 0);
ck_assert_int_eq(xrandr_stub_log()->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
/* --- the out-of-bounds walk --- */
/* info->ncrtc bounds info->crtcs, not screen->crtcs. Give the output
* four crtcs and the screen one: walking the screen's array reads
* three elements past its end, and every id it comes back with is the
* wrong one. */
START_TEST (test_orientation_walks_output_crtcs)
{
xrandr_stub_cfg_t cfg;
const xrandr_stub_log_t *log;
void *data;
int i;
cfg_default(&cfg);
cfg.screen_ncrtc = 1;
cfg.output_ncrtc = 4;
xrandr_stub_reset(&cfg);
data = serv_init();
dispserv_x11.get_screen_orientation(data);
serv_free(data);
log = xrandr_stub_log();
ck_assert_int_eq(log->get_crtc_info_calls, 4);
for (i = 0; i < log->get_crtc_info_calls; i++)
ck_assert_int_eq((int)log->queried_crtcs[i],
XRANDR_STUB_OUTPUT_CRTC_BASE + i);
ck_assert_int_eq(log->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
/* The set path compounded it: it queried one crtc and then handed a
* different one to XRRSetCrtcConfig(), configuring a crtc from
* another's geometry. Whatever it queries, it must configure. */
START_TEST (test_set_orientation_configures_queried_crtcs)
{
xrandr_stub_cfg_t cfg;
const xrandr_stub_log_t *log;
void *data;
int i;
cfg_default(&cfg);
cfg.screen_ncrtc = 1;
cfg.output_ncrtc = 4;
xrandr_stub_reset(&cfg);
data = serv_init();
dispserv_x11.set_screen_orientation(data, ORIENTATION_VERTICAL);
serv_free(data);
/* The walk disables a crtc and then reconfigures it, so there are
* two set calls per queried crtc; what matters is that every id it
* configures is one it actually described, i.e. one of the
* output's, never a screen crtc id it never looked at. */
log = xrandr_stub_log();
ck_assert_int_gt(log->set_crtc_config_calls, 0);
for (i = 0; i < log->set_crtc_config_calls; i++)
{
int j, found = 0;
for (j = 0; j < log->get_crtc_info_calls; j++)
if (log->configured_crtcs[i] == log->queried_crtcs[j])
found = 1;
ck_assert_msg(found,
"configured crtc %d was never queried",
(int)log->configured_crtcs[i]);
}
ck_assert_int_eq(log->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
START_TEST (test_set_orientation_output_info_null)
{
xrandr_stub_cfg_t cfg;
void *data;
cfg_default(&cfg);
cfg.fail_output_info = 1;
xrandr_stub_reset(&cfg);
data = serv_init();
dispserv_x11.set_screen_orientation(data, ORIENTATION_VERTICAL);
serv_free(data);
ck_assert_int_eq(xrandr_stub_log()->set_crtc_config_calls, 0);
ck_assert_int_eq(xrandr_stub_log()->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
START_TEST (test_set_orientation_screen_resources_null)
{
xrandr_stub_cfg_t cfg;
void *data;
cfg_default(&cfg);
cfg.fail_screen_resources = 1;
xrandr_stub_reset(&cfg);
data = serv_init();
dispserv_x11.set_screen_orientation(data, ORIENTATION_VERTICAL);
serv_free(data);
ck_assert_int_eq(xrandr_stub_log()->set_crtc_config_calls, 0);
ck_assert_int_eq(xrandr_stub_log()->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
/* --- set_resolution --- */
START_TEST (test_set_resolution_output_info_null)
{
xrandr_stub_cfg_t cfg;
void *data;
cfg_default(&cfg);
cfg.fail_output_info = 1;
xrandr_stub_reset(&cfg);
data = serv_init();
dispserv_x11.set_resolution(data, 1280, 720, 60, 60.0f, 0, 0, 0, 0);
serv_free(data);
ck_assert_int_eq(xrandr_stub_log()->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
START_TEST (test_set_resolution_crtc_info_null)
{
xrandr_stub_cfg_t cfg;
void *data;
cfg_default(&cfg);
cfg.fail_crtc_info = 1;
xrandr_stub_reset(&cfg);
data = serv_init();
dispserv_x11.set_resolution(data, 1280, 720, 60, 60.0f, 0, 0, 0, 0);
serv_free(data);
ck_assert_int_eq(xrandr_stub_log()->set_crtc_config_calls, 0);
ck_assert_int_eq(xrandr_stub_log()->bad_free, 0);
ck_assert(xrandr_stub_all_freed());
}
END_TEST
Suite *create_suite(void)
{
Suite *s = suite_create(SUITE_NAME);
TCase *tc_core = tcase_create("Core");
tcase_add_test(tc_core, test_orientation_output_info_null);
tcase_add_test(tc_core, test_orientation_crtc_info_null);
tcase_add_test(tc_core, test_orientation_screen_resources_null);
tcase_add_test(tc_core, test_orientation_screen_info_null);
tcase_add_test(tc_core, test_orientation_output_disconnected);
tcase_add_test(tc_core, test_orientation_walks_output_crtcs);
tcase_add_test(tc_core, test_set_orientation_configures_queried_crtcs);
tcase_add_test(tc_core, test_set_orientation_output_info_null);
tcase_add_test(tc_core, test_set_orientation_screen_resources_null);
tcase_add_test(tc_core, test_set_resolution_output_info_null);
tcase_add_test(tc_core, test_set_resolution_crtc_info_null);
suite_add_tcase(s, tc_core);
return s;
}
int main(void)
{
int num_fail;
Suite *s = create_suite();
SRunner *sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
num_fail = srunner_ntests_failed(sr);
srunner_free(sr);
return (num_fail == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
+371
View File
@@ -0,0 +1,371 @@
/* Copyright (C) 2010-2025 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (xrandr_stub.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlibint.h>
#include "xrandr_stub.h"
#define STUB_MAX_ALLOCS 64
static xrandr_stub_cfg_t s_cfg;
static xrandr_stub_log_t s_log;
/* Every pointer handed to the code under test, so a double free or a
* free of something never allocated is caught rather than left to
* ASan to find in whichever test happens to run last. */
static void *s_allocs[STUB_MAX_ALLOCS];
static int s_alloc_freed[STUB_MAX_ALLOCS];
static int s_alloc_count;
/* A Display that DefaultRootWindow(), DefaultScreen(), DisplayWidth()
* and friends can be expanded against. Those are macros reaching
* straight into the struct, so there is no intercepting them; the
* struct has to be real enough to index. */
static Display *s_dpy;
static Screen s_screen;
/* Name of the mode most recently passed to XRRCreateMode(). */
static char s_mode_name[64];
static const char *s_alloc_tag[STUB_MAX_ALLOCS];
static const char *s_tag = "?";
static void *stub_alloc(size_t len)
{
void *p;
if (s_alloc_count >= STUB_MAX_ALLOCS)
return NULL;
if (!(p = calloc(1, len)))
return NULL;
s_allocs[s_alloc_count] = p;
s_alloc_tag[s_alloc_count] = s_tag;
s_alloc_freed[s_alloc_count] = 0;
s_alloc_count++;
return p;
}
static void stub_free(void *p)
{
int i;
if (!p)
{
/* Freeing NULL is what the fixed code must not do, because the
* real XRRFree* entry points do not tolerate it. */
s_log.bad_free++;
return;
}
/* Search from the newest entry: the allocator reuses addresses, so
* the same pointer value can appear more than once in the table and
* only the most recent one is live. */
for (i = s_alloc_count - 1; i >= 0; i--)
{
if (s_allocs[i] != p || s_alloc_freed[i])
continue;
s_alloc_freed[i] = 1;
free(p);
return;
}
/* Either never handed out, or handed out and already freed. */
s_log.bad_free++;
}
void xrandr_stub_reset(const xrandr_stub_cfg_t *cfg)
{
int i;
for (i = 0; i < s_alloc_count; i++)
if (!s_alloc_freed[i])
free(s_allocs[i]);
s_alloc_count = 0;
memset(&s_log, 0, sizeof(s_log));
s_mode_name[0] = '\0';
s_cfg = *cfg;
if (!s_dpy)
{
s_dpy = (Display*)calloc(1, sizeof(Display));
memset(&s_screen, 0, sizeof(s_screen));
s_screen.root = 1;
s_screen.width = 1280;
s_screen.height = 720;
s_screen.mwidth = 340;
s_screen.mheight = 190;
((_XPrivDisplay)s_dpy)->nscreens = 1;
((_XPrivDisplay)s_dpy)->screens = &s_screen;
((_XPrivDisplay)s_dpy)->default_screen = 0;
}
}
const xrandr_stub_log_t *xrandr_stub_log(void) { return &s_log; }
int xrandr_stub_all_freed(void)
{
int i, leaked = 0;
for (i = 0; i < s_alloc_count; i++)
if (!s_alloc_freed[i])
{
fprintf(stderr, " stub: leaked allocation #%d (%s)\n",
i, s_alloc_tag[i]);
leaked++;
}
return leaked == 0;
}
/* --- Xlib --- */
Display *XOpenDisplay(const char *name)
{
(void)name;
if (s_cfg.fail_open_display)
return NULL;
return s_dpy;
}
int XCloseDisplay(Display *dpy) { (void)dpy; return 0; }
int XGrabServer(Display *dpy) { (void)dpy; return 0; }
int XUngrabServer(Display *dpy) { (void)dpy; return 0; }
int XSync(Display *dpy, Bool discard) { (void)dpy; (void)discard; return 0; }
Atom XInternAtom(Display *dpy, const char *name, Bool only_if_exists)
{
(void)dpy; (void)name; (void)only_if_exists;
return 1;
}
int XChangeProperty(Display *dpy, Window w, Atom prop, Atom type, int fmt,
int mode, const unsigned char *data, int nelements)
{
(void)dpy; (void)w; (void)prop; (void)type; (void)fmt; (void)mode;
(void)data; (void)nelements;
return 0;
}
int XDeleteProperty(Display *dpy, Window w, Atom prop)
{
(void)dpy; (void)w; (void)prop;
return 0;
}
/* --- XRandR --- */
XRRScreenResources *XRRGetScreenResources(Display *dpy, Window w)
{
s_tag = "screen_resources";
XRRScreenResources *r;
int i;
(void)dpy; (void)w;
if (s_cfg.fail_screen_resources)
return NULL;
if (!(r = (XRRScreenResources*)stub_alloc(sizeof(*r))))
return NULL;
r->noutput = 1;
if (!(r->outputs = (RROutput*)stub_alloc(sizeof(RROutput))))
return r;
r->outputs[0] = 1;
r->ncrtc = s_cfg.screen_ncrtc;
if (r->ncrtc > 0)
{
if (!(r->crtcs = (RRCrtc*)stub_alloc(sizeof(RRCrtc) * r->ncrtc)))
return r;
for (i = 0; i < r->ncrtc; i++)
r->crtcs[i] = XRANDR_STUB_SCREEN_CRTC_BASE + i;
}
/* Report the mode XRRCreateMode() was last asked for, so callers
* that create a mode and then look it up again find it, as they
* would against a real server. */
if (s_mode_name[0])
{
if ((r->modes = (XRRModeInfo*)stub_alloc(sizeof(XRRModeInfo))))
{
r->nmode = 1;
r->modes[0].id = 42;
r->modes[0].name = s_mode_name;
r->modes[0].nameLength = (int)strlen(s_mode_name);
r->modes[0].width = 1280;
r->modes[0].height = 720;
}
}
return r;
}
XRRScreenResources *XRRGetScreenResourcesCurrent(Display *dpy, Window w)
{
return XRRGetScreenResources(dpy, w);
}
void XRRFreeScreenResources(XRRScreenResources *r)
{
s_log.free_screen_resources_calls++;
if (r)
{
if (r->modes)
stub_free(r->modes);
if (r->crtcs)
stub_free(r->crtcs);
if (r->outputs)
stub_free(r->outputs);
}
stub_free(r);
}
XRRScreenConfiguration *XRRGetScreenInfo(Display *dpy, Window w)
{
s_tag = "screen_info";
(void)dpy; (void)w;
if (s_cfg.fail_screen_info)
return NULL;
/* Opaque to the caller; only ever passed back to the free. */
return (XRRScreenConfiguration*)stub_alloc(sizeof(void*));
}
void XRRFreeScreenConfigInfo(XRRScreenConfiguration *c)
{
s_log.free_screen_config_calls++;
stub_free(c);
}
XRROutputInfo *XRRGetOutputInfo(Display *dpy, XRRScreenResources *res,
RROutput out)
{
s_tag = "output_info";
XRROutputInfo *o;
int i;
(void)dpy; (void)res; (void)out;
if (s_cfg.fail_output_info)
return NULL;
if (!(o = (XRROutputInfo*)stub_alloc(sizeof(*o))))
return NULL;
o->connection = (unsigned short)s_cfg.output_connection;
o->name = (char*)"STUB-1";
o->nameLen = 6;
o->crtc = XRANDR_STUB_OUTPUT_CRTC_BASE;
o->ncrtc = s_cfg.output_ncrtc;
if (o->ncrtc > 0)
{
if (!(o->crtcs = (RRCrtc*)stub_alloc(sizeof(RRCrtc) * o->ncrtc)))
return o;
for (i = 0; i < o->ncrtc; i++)
o->crtcs[i] = XRANDR_STUB_OUTPUT_CRTC_BASE + i;
}
o->nmode = 0;
return o;
}
void XRRFreeOutputInfo(XRROutputInfo *o)
{
s_log.free_output_info_calls++;
if (o && o->crtcs)
stub_free(o->crtcs);
stub_free(o);
}
XRRCrtcInfo *XRRGetCrtcInfo(Display *dpy, XRRScreenResources *res, RRCrtc crtc)
{
s_tag = "crtc_info";
XRRCrtcInfo *c;
(void)dpy; (void)res;
if (s_log.get_crtc_info_calls
< (int)(sizeof(s_log.queried_crtcs) / sizeof(s_log.queried_crtcs[0])))
s_log.queried_crtcs[s_log.get_crtc_info_calls] = crtc;
s_log.get_crtc_info_calls++;
if (s_cfg.fail_crtc_info)
return NULL;
if (!(c = (XRRCrtcInfo*)stub_alloc(sizeof(*c))))
return NULL;
c->width = 1280;
c->height = 720;
c->rotation = RR_Rotate_0;
c->mode = 1;
c->noutput = 0;
return c;
}
void XRRFreeCrtcInfo(XRRCrtcInfo *c)
{
s_log.free_crtc_info_calls++;
stub_free(c);
}
Status XRRSetCrtcConfig(Display *dpy, XRRScreenResources *res, RRCrtc crtc,
Time ts, int x, int y, RRMode mode, Rotation rot,
RROutput *outputs, int noutputs)
{
(void)dpy; (void)res; (void)ts; (void)x; (void)y; (void)mode; (void)rot;
(void)outputs; (void)noutputs;
if (s_log.set_crtc_config_calls
< (int)(sizeof(s_log.configured_crtcs) / sizeof(s_log.configured_crtcs[0])))
s_log.configured_crtcs[s_log.set_crtc_config_calls] = crtc;
s_log.set_crtc_config_calls++;
return 0;
}
void XRRSetScreenSize(Display *dpy, Window w, int width, int height,
int mmWidth, int mmHeight)
{
(void)dpy; (void)w; (void)width; (void)height; (void)mmWidth; (void)mmHeight;
}
RRMode XRRCreateMode(Display *dpy, Window w, XRRModeInfo *info)
{
(void)dpy; (void)w;
if (info && info->name)
{
size_t n = sizeof(s_mode_name) - 1;
strncpy(s_mode_name, info->name, n);
s_mode_name[n] = '\0';
}
return 42;
}
void XRRDestroyMode(Display *dpy, RRMode mode) { (void)dpy; (void)mode; }
void XRRAddOutputMode(Display *dpy, RROutput out, RRMode mode)
{
(void)dpy; (void)out; (void)mode;
}
void XRRDeleteOutputMode(Display *dpy, RROutput out, RRMode mode)
{
(void)dpy; (void)out; (void)mode;
}
+92
View File
@@ -0,0 +1,92 @@
/* Copyright (C) 2010-2025 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (xrandr_stub.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __XRANDR_STUB_H
#define __XRANDR_STUB_H
/* Knobs and observations shared between the stub X server in
* xrandr_stub.c and the tests that drive it.
*
* The stub replaces libXrandr and the handful of Xlib entry points
* dispserv_x11.c reaches, so the tests run with no X server, no RandR
* extension and no display of any kind. That is the point: the
* behaviour under test is what happens when those queries *fail*, and
* a working X server is precisely the environment that cannot produce
* it.
*/
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
/* Knobs: set before calling into dispserv_x11. */
typedef struct
{
/* Make the corresponding query hand back NULL. */
int fail_open_display;
int fail_screen_resources;
int fail_screen_info;
int fail_output_info;
int fail_crtc_info;
/* Number of crtcs the *screen* advertises, and the number the
* *output* advertises. Making the output's larger is what
* distinguishes indexing info->crtcs from indexing screen->crtcs:
* the latter runs off the end of the screen's array. */
int screen_ncrtc;
int output_ncrtc;
/* RR_Connected or RR_Disconnected. */
int output_connection;
} xrandr_stub_cfg_t;
/* Observations: read after. */
typedef struct
{
int get_crtc_info_calls;
int set_crtc_config_calls;
int free_crtc_info_calls;
int free_output_info_calls;
int free_screen_resources_calls;
int free_screen_config_calls;
/* Every crtc id passed to XRRGetCrtcInfo()/XRRSetCrtcConfig().
* The stub numbers the screen's crtcs from XRANDR_STUB_SCREEN_CRTC_BASE
* and the output's from XRANDR_STUB_OUTPUT_CRTC_BASE, so which array
* the code walked is readable straight off these. */
RRCrtc queried_crtcs[16];
RRCrtc configured_crtcs[16];
/* Set if the stub was asked to free a pointer it never handed out,
* or to free the same one twice. */
int bad_free;
} xrandr_stub_log_t;
#define XRANDR_STUB_SCREEN_CRTC_BASE 100
#define XRANDR_STUB_OUTPUT_CRTC_BASE 200
void xrandr_stub_reset(const xrandr_stub_cfg_t *cfg);
const xrandr_stub_log_t *xrandr_stub_log(void);
/* Non-zero if every pointer the stub handed out has been freed. */
int xrandr_stub_all_freed(void);
#endif