/** * 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 #include #include #include #include #include #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 \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; }