mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-15 03:37:09 -04:00
154 lines
6.6 KiB
Python
154 lines
6.6 KiB
Python
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SOURCE = ROOT / 'app/src/main/cpp/waylandcomp/src/compositor.c'
|
|
|
|
|
|
def extract_function(source, name):
|
|
match = re.search(r'^(?:static )?(?:void|int|uint32_t) ' + re.escape(name) + r'\([^;]*?\) \{',
|
|
source, re.MULTILINE)
|
|
if match is None:
|
|
raise AssertionError(f'Missing compositor function: {name}')
|
|
cursor, depth = match.end(), 1
|
|
while depth:
|
|
depth += (source[cursor] == '{') - (source[cursor] == '}')
|
|
cursor += 1
|
|
return source[match.start():cursor]
|
|
|
|
|
|
PREAMBLE = r'''
|
|
#define _GNU_SOURCE
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdatomic.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
struct wl_resource { int version, modes, done, hz, w, h; struct wl_resource *next; };
|
|
struct surface { int xdg_toplevel, toplevel_committed; struct surface *next; };
|
|
struct input_msg { int type, p1, p2, p3, p4; };
|
|
static struct wl_resource second = { .version = 1 };
|
|
static struct wl_resource first = { .version = 4, .next = &second };
|
|
static struct surface surface = { .xdg_toplevel = 1, .toplevel_committed = 1 };
|
|
static int g_input_pipe[2] = {-1, -1};
|
|
static atomic_int g_output_refresh_mhz = 60000;
|
|
static atomic_int g_output_refresh_pending;
|
|
static int g_output_announced_refresh_mhz = 60000;
|
|
static int g_output_w = 1920, g_output_h = 1080;
|
|
static int flushes, resizes, pointers, ticks;
|
|
static void *g_display;
|
|
#define WL_OUTPUT_MODE_CURRENT 1
|
|
#define WL_OUTPUT_MODE_PREFERRED 2
|
|
#define wl_resource_for_each(r, list) for ((r) = &first; (r); (r) = (r)->next)
|
|
#define wl_list_for_each(s, list, link) for ((s) = &surface; (s); (s) = (s)->next)
|
|
static int wl_resource_get_version(struct wl_resource *r) { return r->version; }
|
|
static void wl_output_send_mode(struct wl_resource *r, int flags, int w, int h, int hz) {
|
|
assert(flags == 3); r->modes++; r->hz = hz; r->w = w; r->h = h;
|
|
}
|
|
static void wl_output_send_done(struct wl_resource *r) { r->done++; }
|
|
static void wl_display_flush_clients(void *display) { (void)display; flushes++; }
|
|
static void send_toplevel_configure(struct surface *s) { (void)s; resizes++; }
|
|
#define droiddeck_log(...) ((void)0)
|
|
#define deliver_key(m) ((void)0)
|
|
#define pointer_event(...) ((void)0)
|
|
#define pointer_button(...) ((void)0)
|
|
#define scroll_event(...) ((void)0)
|
|
#define pointer_delta(...) ((void)0)
|
|
#define deliver_touch(m) ((void)0)
|
|
#define deliver_pointer(m) (pointers++)
|
|
static void on_vsync(int64_t ns) { assert(ns > 0); ticks++; }
|
|
'''
|
|
|
|
CHECKS = r'''
|
|
static int queued(void) { int n; assert(ioctl(g_input_pipe[0], FIONREAD, &n) == 0); return n; }
|
|
static void drain(void) { on_input_readable(g_input_pipe[0], 0, NULL); }
|
|
static void expect(int modes, int hz, int resize_count) {
|
|
assert(first.modes == modes && second.modes == modes);
|
|
assert(first.hz == hz && second.hz == hz);
|
|
assert(first.w == 1920 && second.w == 1920 && first.h == 1080 && second.h == 1080);
|
|
assert(first.done == modes && second.done == 0);
|
|
assert(resizes == resize_count);
|
|
assert(pointers == 0);
|
|
}
|
|
int main(void) {
|
|
droiddeck_wayland_set_output_refresh_rate(120000);
|
|
assert(atomic_load(&g_output_refresh_mhz) == 120000);
|
|
assert(!atomic_load(&g_output_refresh_pending));
|
|
assert(first.modes == 0 && second.modes == 0);
|
|
assert(pipe2(g_input_pipe, O_CLOEXEC | O_NONBLOCK) == 0);
|
|
g_output_announced_refresh_mhz = atomic_load(&g_output_refresh_mhz);
|
|
droiddeck_wayland_set_output_refresh_rate(120000);
|
|
assert(queued() == 0);
|
|
droiddeck_wayland_set_output_refresh_rate(60000);
|
|
assert(first.modes == 0 && queued() == sizeof(struct input_msg));
|
|
drain(); expect(1, 60000, 0); assert(flushes == 1);
|
|
droiddeck_wayland_set_output_refresh_rate(60000);
|
|
assert(queued() == 0); drain(); expect(1, 60000, 0);
|
|
droiddeck_wayland_set_output_refresh_rate(90000);
|
|
droiddeck_wayland_set_output_refresh_rate(120000);
|
|
drain(); expect(2, 120000, 0);
|
|
droiddeck_wayland_set_output_refresh_rate(90000);
|
|
droiddeck_wayland_set_output_refresh_rate(120000);
|
|
drain(); expect(2, 120000, 0);
|
|
droiddeck_wayland_set_output_refresh_rate(60000);
|
|
droiddeck_wayland_output_resized();
|
|
drain(); expect(3, 60000, 1); assert(flushes == 3);
|
|
struct input_msg wake = {9, 0, 0, 0, 0};
|
|
int n = 0;
|
|
while (write(g_input_pipe[1], &wake, sizeof(wake)) == sizeof(wake)) n++;
|
|
assert(n > 0 && errno == EAGAIN);
|
|
droiddeck_wayland_set_output_refresh_rate(120000);
|
|
drain(); expect(4, 120000, 1);
|
|
droiddeck_wayland_set_output_refresh_rate(0);
|
|
drain(); expect(5, 60000, 1);
|
|
droiddeck_wayland_set_output_refresh_rate(-1);
|
|
assert(queued() == 0);
|
|
struct input_msg vsync = {5, 0, 16000000, 0, 0};
|
|
assert(write(g_input_pipe[1], &vsync, sizeof(vsync)) == sizeof(vsync));
|
|
drain(); expect(5, 60000, 1); assert(ticks == 1);
|
|
assert(nominal_refresh_ns() == 16666666);
|
|
assert(flushes == 5);
|
|
close(g_input_pipe[0]); close(g_input_pipe[1]);
|
|
puts("PASS");
|
|
}
|
|
'''
|
|
|
|
|
|
@unittest.skipUnless(sys.platform.startswith('linux'), 'needs Linux nonblocking pipes')
|
|
class OutputRefreshTest(unittest.TestCase):
|
|
def test_refresh_notifications_preserve_client_pacing_without_resizing(self):
|
|
compiler = shutil.which(os.environ.get('CC', 'cc'))
|
|
if compiler is None:
|
|
self.skipTest('A host C compiler is required')
|
|
source = SOURCE.read_text()
|
|
functions = ('nominal_refresh_ns', 'output_send_mode', 'output_notify_mode',
|
|
'output_resized', 'on_input_readable', 'droiddeck_wayland_output_resized',
|
|
'droiddeck_wayland_set_output_refresh_rate')
|
|
harness = PREAMBLE + '\n'.join(extract_function(source, name) for name in functions) + CHECKS
|
|
with tempfile.TemporaryDirectory(prefix='droiddeck-output-refresh-') as folder:
|
|
path = Path(folder)
|
|
(path / 'refresh.c').write_text(harness)
|
|
binary = path / 'refresh-test'
|
|
build = subprocess.run([compiler, '-std=c11', '-Wall', '-Wextra', '-Werror',
|
|
'-Wno-unused-parameter', '-O2', str(path / 'refresh.c'),
|
|
'-o', str(binary)], capture_output=True, text=True)
|
|
self.assertEqual(0, build.returncode, build.stderr)
|
|
result = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertEqual('PASS', result.stdout.strip())
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|