[client] control: test config and resize messages

This commit is contained in:
Geoffrey McRae
2026-08-12 04:11:46 +10:00
parent 7dc6bc8527
commit 6c0f8d8a28
2 changed files with 511 additions and 0 deletions

View File

@@ -366,6 +366,44 @@ foreach(name IN LISTS FRAME_SCHEDULER_CASES)
) )
endforeach() endforeach()
add_executable(config-message-tests
config_message_test.c
../src/message.c
../src/core.c
../src/config.c
)
target_compile_definitions(config-message-tests PRIVATE
CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1
)
target_include_directories(config-message-tests PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
"${PROJECT_TOP}/repos/gui/cimgui"
"${PROJECT_TOP}/repos/gui/cimgui/imgui"
)
target_link_libraries(config-message-tests
${EXE_FLAGS}
lg_common
)
set(CONFIG_MESSAGE_CASES
debounce
latest
equal
reinit
control
cfg-values
cfg-position
cfg-size
cfg-precedence
)
foreach(name IN LISTS CONFIG_MESSAGE_CASES)
add_test(NAME config-message-${name}
COMMAND config-message-tests ${name}
)
set_tests_properties(config-message-${name} PROPERTIES
TIMEOUT 10
)
endforeach()
if(ENABLE_AUDIO) if(ENABLE_AUDIO)
pkg_check_modules(AUDIO_TEST_SAMPLERATE REQUIRED IMPORTED_TARGET pkg_check_modules(AUDIO_TEST_SAMPLERATE REQUIRED IMPORTED_TARGET
samplerate) samplerate)

View File

@@ -0,0 +1,473 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include "kb.h"
#include "main.h"
#include "message.h"
#include "test.h"
#include "common/debug.h"
#include "common/option.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define MAX_CTL 16U
#define DEBOUNCE_US UINT64_C(500000)
#define TEST_ALARM_S 5U
struct LG_Transport
{
int unused;
};
struct Trace
{
uint64_t now;
unsigned int send;
LG_TransportStatus status;
LG_TransportControl ctl[MAX_CTL];
LG_TransportControlToken token[MAX_CTL];
};
static struct Trace t;
static struct LG_Transport tr;
struct AppState g_state;
struct CursorState g_cursor;
struct AppParams g_params;
const char * linux_to_str[KEY_MAX] =
{
[KEY_A] = "KEY_A",
[KEY_RIGHTCTRL] = "KEY_RIGHTCTRL",
};
static const char * renAName(void)
{
return "EGL";
}
static const char * renBName(void)
{
return "OpenGL";
}
static LG_RendererOps renA =
{
.getName = renAName,
};
static LG_RendererOps renB =
{
.getName = renBName,
};
LG_RendererOps * LG_Renderers[] =
{
&renA,
&renB,
NULL,
};
bool lgTransport_isValid(const char * name)
{
return name &&
(strcmp(name, "lgmp") == 0 || strcmp(name, "spice") == 0);
}
int clock_gettime(clockid_t id, struct timespec * ts)
{
const uint64_t ns = t.now * UINT64_C(1000);
ts->tv_sec = ns / UINT64_C(1000000000);
ts->tv_nsec = ns % UINT64_C(1000000000);
return 0;
}
static LG_TransportStatus sendCtl(LG_Transport * transport,
const LG_TransportControl * control, LG_TransportControlToken * token)
{
CHECK(transport == &tr);
CHECK(control);
CHECK(token);
CHECK(t.send < MAX_CTL);
const unsigned int no = t.send++;
t.ctl[no] = *control;
t.token[no] = no + 1;
*token = t.token[no];
return t.status;
}
static const LG_TransportOps trOps =
{
.name = "fake",
.sendControl = sendCtl,
};
static void msgInit(void)
{
t.now = UINT64_C(1000000);
t.status = LG_TRANSPORT_OK;
g_state.transport.handle = &tr;
g_state.transport.ops = &trOps;
g_state.transportFeatures = LG_TRANSPORT_FEATURE_WINDOW_SIZE;
g_state.srcSize = (struct Point) { 1920, 1080 };
CHECK(lgMessage_init());
}
static void post(unsigned int width, unsigned int height)
{
const LGMsg msg =
{
.type = LG_MSG_WINDOWSIZE,
.windowSize =
{
.width = width,
.height = height,
},
};
lgMessage_post(&msg);
}
static void step(uint64_t us)
{
t.now += us;
}
static void mature(void)
{
step(DEBOUNCE_US);
lgMessage_process();
}
static void checkCtl(unsigned int no, unsigned int width,
unsigned int height)
{
CHECK(no < t.send);
CHECK(t.ctl[no].type == LG_TRANSPORT_CONTROL_WINDOW_SIZE);
CHECK(t.ctl[no].windowSize.width == width);
CHECK(t.ctl[no].windowSize.height == height);
CHECK(t.token[no] == no + 1);
}
static void testDebounce(void)
{
msgInit();
post(800, 600);
lgMessage_process();
CHECK(t.send == 0);
step(DEBOUNCE_US - 1);
lgMessage_process();
CHECK(t.send == 0);
step(1);
lgMessage_process();
CHECK(t.send == 1);
checkCtl(0, 800, 600);
post(800, 600);
mature();
CHECK(t.send == 1);
post(801, 600);
mature();
CHECK(t.send == 2);
checkCtl(1, 801, 600);
lgMessage_deinit();
}
static void testLatest(void)
{
msgInit();
post(800, 600);
step(100);
post(1024, 768);
lgMessage_process();
CHECK(t.send == 0);
step(DEBOUNCE_US - 1);
lgMessage_process();
CHECK(t.send == 0);
step(1);
lgMessage_process();
CHECK(t.send == 1);
checkCtl(0, 1024, 768);
lgMessage_deinit();
}
static void testEqual(void)
{
msgInit();
post(800, 600);
post(1024, 768);
mature();
CHECK(t.send == 1);
/* Posting order decides when timestamps are equal. */
checkCtl(0, 1024, 768);
lgMessage_deinit();
}
static void testReinit(void)
{
msgInit();
post(640, 480);
lgMessage_deinit();
CHECK(lgMessage_init());
mature();
CHECK(t.send == 0);
post(640, 480);
mature();
CHECK(t.send == 1);
lgMessage_deinit();
CHECK(lgMessage_init());
post(640, 480);
mature();
/* A new message instance must not inherit its old duplicate filter. */
CHECK(t.send == 2);
checkCtl(1, 640, 480);
lgMessage_deinit();
}
static void testControl(void)
{
msgInit();
g_state.transportFeatures = 0;
post(100, 100);
mature();
CHECK(t.send == 0);
g_state.transportFeatures = LG_TRANSPORT_FEATURE_WINDOW_SIZE;
g_state.transport.handle = NULL;
post(200, 200);
mature();
CHECK(t.send == 0);
g_state.transport.handle = &tr;
g_state.srcSize = (struct Point) { 300, 300 };
post(300, 300);
mature();
CHECK(t.send == 0);
t.status = LG_TRANSPORT_ERROR;
post(400, 400);
mature();
CHECK(t.send == 1);
checkCtl(0, 400, 400);
step(DEBOUNCE_US);
lgMessage_process();
CHECK(t.send == 1);
t.status = LG_TRANSPORT_UNAVAILABLE;
post(500, 500);
mature();
CHECK(t.send == 2);
checkCtl(1, 500, 500);
t.status = LG_TRANSPORT_OK;
post(600, 600);
mature();
CHECK(t.send == 3);
checkCtl(2, 600, 600);
lgMessage_deinit();
}
static struct Option * opt(const char * module, const char * name)
{
struct Option * result = option_get(module, name);
CHECK(result);
return result;
}
static void checkStr(char * value, const char * expected)
{
CHECK(value);
CHECK(strcmp(value, expected) == 0);
free(value);
}
static void testCfgValues(void)
{
config_init();
struct Option * pos = opt("win", "position");
CHECK(pos->parser(pos, "center"));
CHECK(g_params.center);
checkStr(pos->toString(pos), "center");
CHECK(pos->parser(pos, "-10x20"));
CHECK(!g_params.center);
CHECK(g_params.x == -10);
CHECK(g_params.y == 20);
checkStr(pos->toString(pos), "-10x20");
struct Option * size = opt("win", "size");
CHECK(size->parser(size, "1920x1080"));
CHECK(g_params.w == 1920);
CHECK(g_params.h == 1080);
checkStr(size->toString(size), "1920x1080");
struct Option * mic = opt("audio", "micDefault");
CHECK(mic->parser(mic, "ALLOW"));
CHECK(g_params.micDefaultState == MIC_DEFAULT_ALLOW);
checkStr(mic->toString(mic), "allow");
CHECK(!mic->parser(mic, "sometimes"));
struct Option * resampler = opt("audio", "resampler");
CHECK(resampler->parser(resampler, "BACKEND"));
CHECK(g_params.audioResampler == AUDIO_RESAMPLER_BACKEND);
checkStr(resampler->toString(resampler), "backend");
CHECK(!resampler->parser(resampler, "linear"));
struct Option * renderer = opt("app", "renderer");
CHECK(renderer->parser(renderer, "OpenGL"));
CHECK(g_params.forceRenderer);
CHECK(g_params.forceRendererIndex == 1);
checkStr(renderer->toString(renderer), "OpenGL");
CHECK(renderer->parser(renderer, "auto"));
CHECK(!g_params.forceRenderer);
checkStr(renderer->toString(renderer), "auto");
struct Option * key = opt("input", "escapeKey");
CHECK(key->parser(key, "KEY_A"));
CHECK(key->value.x_int == KEY_A);
char * keyName = key->toString(key);
CHECK(keyName);
CHECK(strstr(keyName, "KEY_A"));
free(keyName);
struct Option * rotate = opt("win", "rotate");
const char * error = NULL;
rotate->value.x_int = 270;
CHECK(rotate->validator(rotate, &error));
rotate->value.x_int = 45;
CHECK(!rotate->validator(rotate, &error));
CHECK(error);
config_free();
}
static void testCfgPos(void)
{
config_init();
struct Option * pos = opt("win", "position");
CHECK(!pos->parser(pos, NULL));
CHECK(!pos->parser(pos, "10"));
CHECK(!pos->parser(pos, "10x20tail"));
config_free();
}
static void testCfgSize(void)
{
config_init();
struct Option * size = opt("win", "size");
CHECK(!size->parser(size, NULL));
CHECK(!size->parser(size, "0x20"));
CHECK(!size->parser(size, "-1x20"));
CHECK(!size->parser(size, "10x20tail"));
config_free();
}
static void loadCfg(const char * text)
{
char path[] = "/tmp/lg-config-test-XXXXXX";
const int fd = mkstemp(path);
CHECK(fd >= 0);
const size_t size = strlen(text);
CHECK(write(fd, text, size) == (ssize_t)size);
CHECK(close(fd) == 0);
CHECK(option_load(path));
CHECK(unlink(path) == 0);
}
static void testCfgPrecedence(void)
{
config_init();
loadCfg("[win]\nsize=800x600\nposition=10x20\n");
CHECK(g_params.w == 800);
CHECK(g_params.h == 600);
CHECK(g_params.x == 10);
CHECK(g_params.y == 20);
char * argv[] =
{
"test",
"win:size=1024x768",
"win:position=center",
};
CHECK(option_parse(3, argv));
CHECK(g_params.w == 1024);
CHECK(g_params.h == 768);
CHECK(g_params.center);
loadCfg("[win]\nsize=1280x720\nposition=30x40\n");
CHECK(g_params.w == 1280);
CHECK(g_params.h == 720);
CHECK(g_params.x == 30);
CHECK(g_params.y == 40);
CHECK(!g_params.center);
CHECK(option_validate());
config_free();
}
struct Test
{
const char * name;
void (*run)(void);
};
static const struct Test tests[] =
{
{ "debounce" , testDebounce },
{ "latest" , testLatest },
{ "equal" , testEqual },
{ "reinit" , testReinit },
{ "control" , testControl },
{ "cfg-values" , testCfgValues },
{ "cfg-position" , testCfgPos },
{ "cfg-size" , testCfgSize },
{ "cfg-precedence", testCfgPrecedence },
};
int main(int argc, char ** argv)
{
if (argc != 2)
{
fprintf(stderr, "usage: %s <case>\n", argv[0]);
return EXIT_FAILURE;
}
alarm(TEST_ALARM_S);
debug_init();
for (unsigned int i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i)
if (strcmp(argv[1], tests[i].name) == 0)
{
tests[i].run();
return 0;
}
fprintf(stderr, "unknown test: %s\n", argv[1]);
return EXIT_FAILURE;
}