From 5afeeb531a985db5677fbf311361ecaad836e93a Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 13 Sep 2019 10:33:49 +0300 Subject: [PATCH] Add test framework When testing the library, the libdrm dependency is swapped with a mock libdrm. The test sets up the mock libdrm's internal state and then runs the test. --- meson.build | 2 + test/include/libdrm_mock.h | 20 ++ test/libdrm_mock.c | 372 +++++++++++++++++++++++++++++++++++++ test/meson.build | 27 +++ test/test_alloc.c | 61 ++++++ 5 files changed, 482 insertions(+) create mode 100644 test/include/libdrm_mock.h create mode 100644 test/libdrm_mock.c create mode 100644 test/meson.build create mode 100644 test/test_alloc.c diff --git a/meson.build b/meson.build index e997c77..a461e82 100644 --- a/meson.build +++ b/meson.build @@ -58,3 +58,5 @@ pkgconfig.generate( name: meson.project_name(), description: 'Hardware composer library', ) + +subdir('test') diff --git a/test/include/libdrm_mock.h b/test/include/libdrm_mock.h new file mode 100644 index 0000000..97fc027 --- /dev/null +++ b/test/include/libdrm_mock.h @@ -0,0 +1,20 @@ +#ifndef LIFTOFF_LIBDRM_MOCK_H +#define LIFTOFF_LIBDRM_MOCK_H + +#include +#include +#include + +extern uint32_t liftoff_mock_drm_crtc_id; + +struct liftoff_layer; + +int liftoff_mock_drm_open(void); +uint32_t liftoff_mock_drm_create_fb(struct liftoff_layer *layer); +struct liftoff_mock_plane *liftoff_mock_drm_create_plane(int type); +void liftoff_mock_plane_add_compatible_layer(struct liftoff_mock_plane *plane, + struct liftoff_layer *layer); +struct liftoff_layer *liftoff_mock_plane_get_layer(struct liftoff_mock_plane *plane, + drmModeAtomicReq *req); + +#endif diff --git a/test/libdrm_mock.c b/test/libdrm_mock.c new file mode 100644 index 0000000..1a1da6c --- /dev/null +++ b/test/libdrm_mock.c @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include +#include +#include "libdrm_mock.h" + +#define MAX_PLANES 64 +#define MAX_LAYERS 64 +#define MAX_PROPS 64 + +uint32_t liftoff_mock_drm_crtc_id = 0xCC000000; + +struct liftoff_mock_plane { + uint32_t id; + int type; + struct liftoff_layer *compatible_layers[MAX_LAYERS]; +}; + +struct liftoff_mock_prop { + uint32_t obj_id, prop_id; + uint64_t value; +}; + +struct _drmModeAtomicReq { + struct liftoff_mock_prop props[MAX_PROPS]; + int cursor; +}; + +static int mock_pipe[2] = {-1, -1}; +static struct liftoff_mock_plane mock_planes[MAX_PLANES]; +static struct liftoff_layer *mock_fbs[MAX_LAYERS]; + +enum plane_prop { + PLANE_TYPE, + PLANE_FB_ID, + PLANE_CRTC_ID, +}; + +static const char *plane_props[] = { + [PLANE_TYPE] = "type", + [PLANE_FB_ID] = "FB_ID", + [PLANE_CRTC_ID] = "CRTC_ID", + "CRTC_X", + "CRTC_Y", + "CRTC_W", + "CRTC_H", + "SRC_X", + "SRC_Y", + "SRC_W", + "SRC_H", +}; + +static void assert_drm_fd(int fd) +{ + int ret; + struct stat stat_got, stat_want; + + ret = fstat(mock_pipe[0], &stat_want); + assert(ret == 0); + ret = fstat(fd, &stat_got); + assert(ret == 0); + + assert(stat_got.st_dev == stat_want.st_dev && + stat_got.st_ino == stat_want.st_ino); +} + +int liftoff_mock_drm_open(void) +{ + int ret; + + assert(mock_pipe[0] < 0); + ret = pipe(mock_pipe); + assert(ret == 0); + + return mock_pipe[0]; +} + +struct liftoff_mock_plane *liftoff_mock_drm_create_plane(int type) +{ + struct liftoff_mock_plane *plane; + size_t i; + + assert(mock_pipe[0] < 0); + + i = 0; + plane = &mock_planes[0]; + while (plane->id != 0) { + plane++; + i++; + } + + plane->id = 0xEE000000 + i; + plane->type = type; + return plane; +} + +void liftoff_mock_plane_add_compatible_layer(struct liftoff_mock_plane *plane, + struct liftoff_layer *layer) +{ + size_t i; + + for (i = 0; i < MAX_LAYERS; i++) { + if (plane->compatible_layers[i] == NULL) { + plane->compatible_layers[i] = layer; + return; + } + } + + assert(0); +} + +uint32_t liftoff_mock_drm_create_fb(struct liftoff_layer *layer) +{ + size_t i; + + i = 0; + while (mock_fbs[i] != 0) { + i++; + } + + mock_fbs[i] = layer; + + return 0xFB000000 + i; +} + +static bool mock_atomic_req_get_property(drmModeAtomicReq *req, uint32_t obj_id, + enum plane_prop prop, uint64_t *value) +{ + ssize_t i; + uint32_t prop_id; + + prop_id = 0xB0000000 + prop; + for (i = req->cursor - 1; i >= 0; i--) { + if (req->props[i].obj_id == obj_id && + req->props[i].prop_id == prop_id) { + *value = req->props[i].value; + return true; + } + } + + return false; +} + +static struct liftoff_layer *mock_fb_get_layer(uint32_t fb_id) +{ + size_t i; + + if (fb_id == 0) { + return NULL; + } + + assert((fb_id & 0xFF000000) == 0xFB000000); + + i = fb_id & 0x00FFFFFF; + assert(i < MAX_LAYERS); + + return mock_fbs[i]; +} + +struct liftoff_layer *liftoff_mock_plane_get_layer(struct liftoff_mock_plane *plane, + drmModeAtomicReq *req) +{ + uint64_t fb_id; + + if (!mock_atomic_req_get_property(req, plane->id, PLANE_FB_ID, &fb_id)) { + return NULL; + } + + return mock_fb_get_layer(fb_id); +} + +int drmModeAtomicCommit(int fd, drmModeAtomicReq *req, uint32_t flags, + void *user_data) +{ + size_t i, j; + struct liftoff_mock_plane *plane; + uint64_t fb_id, crtc_id; + bool has_fb, has_crtc, found; + struct liftoff_layer *layer; + + assert_drm_fd(fd); + assert(flags == DRM_MODE_ATOMIC_TEST_ONLY); + + for (i = 0; i < MAX_PLANES; i++) { + plane = &mock_planes[i]; + if (plane->id == 0) { + break; + } + + has_fb = mock_atomic_req_get_property(req, plane->id, + PLANE_FB_ID, &fb_id); + has_crtc = mock_atomic_req_get_property(req, plane->id, + PLANE_CRTC_ID, + &crtc_id); + + if (has_fb != has_crtc) { + return -EINVAL; + } + + if (has_fb) { + if (crtc_id != liftoff_mock_drm_crtc_id) { + return -EINVAL; + } + layer = mock_fb_get_layer(fb_id); + if (layer == NULL) { + return -EINVAL; + } + found = false; + for (j = 0; j < MAX_LAYERS; j++) { + if (plane->compatible_layers[i] == layer) { + found = true; + break; + } + } + if (!found) { + return -EINVAL; + } + } + } + + return 0; +} + +drmModeRes *drmModeGetResources(int fd) +{ + drmModeRes *res; + + assert_drm_fd(fd); + + res = calloc(1, sizeof(*res)); + res->count_crtcs = 1; + res->crtcs = &liftoff_mock_drm_crtc_id; + return res; +} + +void drmModeFreeResources(drmModeRes *res) +{ + free(res); +} + +drmModePlaneRes *drmModeGetPlaneResources(int fd) +{ + static uint32_t plane_ids[MAX_PLANES]; + drmModePlaneRes *res; + size_t i; + + assert_drm_fd(fd); + + for (i = 0; i < MAX_PLANES; i++) { + if (mock_planes[i].id == 0) { + break; + } + plane_ids[i] = mock_planes[i].id; + } + + res = calloc(1, sizeof(*res)); + res->count_planes = i; + res->planes = plane_ids; + return res; +} + +void drmModeFreePlaneResources(drmModePlaneRes *res) +{ + free(res); +} + +drmModePlane *drmModeGetPlane(int fd, uint32_t id) +{ + drmModePlane *plane; + + assert_drm_fd(fd); + + plane = calloc(1, sizeof(*plane)); + plane->plane_id = id; + plane->possible_crtcs = 1 << 0; + return plane; +} + +void drmModeFreePlane(drmModePlane *plane) { + free(plane); +} + +drmModeObjectProperties *drmModeObjectGetProperties(int fd, uint32_t obj_id, + uint32_t obj_type) +{ + struct liftoff_mock_plane *plane; + drmModeObjectProperties *props; + size_t i; + static uint32_t prop_ids[MAX_PROPS]; + + assert_drm_fd(fd); + assert(obj_type == DRM_MODE_OBJECT_PLANE); + + plane = NULL; + for (i = 0; i < MAX_PLANES; i++) { + if (mock_planes[i].id == obj_id) { + plane = &mock_planes[i]; + break; + } + } + assert(plane != NULL); + + props = calloc(1, sizeof(*props)); + props->count_props = sizeof(plane_props) / sizeof(plane_props[0]); + props->props = prop_ids; + for (i = 0; i < props->count_props; i++) { + prop_ids[i] = 0xB0000000 + i; + } + props->prop_values = calloc(props->count_props, sizeof(uint64_t)); + props->prop_values[PLANE_TYPE] = plane->type; + return props; +} + +void drmModeFreeObjectProperties(drmModeObjectProperties *props) { + free(props->prop_values); + free(props); +} + +drmModePropertyRes *drmModeGetProperty(int fd, uint32_t id) +{ + size_t i; + drmModePropertyRes *prop; + + assert_drm_fd(fd); + assert((id & 0xFF000000) == 0xB0000000); + + i = id & 0x00FFFFFF; + assert(i < sizeof(plane_props) / sizeof(plane_props[0])); + + prop = calloc(1, sizeof(*prop)); + prop->prop_id = id; + strncpy(prop->name, plane_props[i], sizeof(prop->name) - 1); + return prop; +} + +void drmModeFreeProperty(drmModePropertyRes *prop) { + free(prop); +} + +drmModeAtomicReq *drmModeAtomicAlloc(void) +{ + return calloc(1, sizeof(drmModeAtomicReq)); +} + +void drmModeAtomicFree(drmModeAtomicReq *req) +{ + free(req); +} + +int drmModeAtomicAddProperty(drmModeAtomicReq *req, uint32_t obj_id, + uint32_t prop_id, uint64_t value) +{ + assert((size_t)req->cursor < sizeof(req->props) / sizeof(req->props[0])); + req->props[req->cursor].obj_id = obj_id; + req->props[req->cursor].prop_id = prop_id; + req->props[req->cursor].value = value; + req->cursor++; + return 0; +} + +int drmModeAtomicGetCursor(drmModeAtomicReq *req) +{ + return req->cursor; +} + +void drmModeAtomicSetCursor(drmModeAtomicReq *req, int cursor) +{ + req->cursor = cursor; +} diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..406d62d --- /dev/null +++ b/test/meson.build @@ -0,0 +1,27 @@ +test_inc = include_directories('include') + +# This mock library will replace libdrm +mock_drm_lib = shared_library( + 'drm', + files('libdrm_mock.c'), + include_directories: [test_inc], + dependencies: drm.partial_dependency(compile_args: true), + soversion: drm.version().split('.')[0], # TODO: get it from the real dep +) + +mock_liftoff = declare_dependency( + link_with: [mock_drm_lib, liftoff_lib], + include_directories: [liftoff_inc, test_inc], + dependencies: drm.partial_dependency(compile_args: true), +) + +test_alloc_exe = executable( + 'test-alloc', + files('test_alloc.c'), + dependencies: mock_liftoff, +) + +test( + 'alloc', + test_alloc_exe, +) diff --git a/test/test_alloc.c b/test/test_alloc.c new file mode 100644 index 0000000..35a99a9 --- /dev/null +++ b/test/test_alloc.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include "libdrm_mock.h" + +static struct liftoff_layer *add_layer(struct liftoff_output *output, + int x, int y, int width, int height) +{ + uint32_t fb_id; + struct liftoff_layer *layer; + + layer = liftoff_layer_create(output); + fb_id = liftoff_mock_drm_create_fb(layer); + liftoff_layer_set_property(layer, "FB_ID", fb_id); + liftoff_layer_set_property(layer, "CRTC_X", x); + liftoff_layer_set_property(layer, "CRTC_Y", y); + liftoff_layer_set_property(layer, "CRTC_W", width); + liftoff_layer_set_property(layer, "CRTC_H", height); + liftoff_layer_set_property(layer, "SRC_X", 0); + liftoff_layer_set_property(layer, "SRC_Y", 0); + liftoff_layer_set_property(layer, "SRC_W", width << 16); + liftoff_layer_set_property(layer, "SRC_H", height << 16); + + return layer; +} + +int main(int argc, char *argv[]) { + struct liftoff_mock_plane *mock_plane; + int drm_fd; + struct liftoff_display *display; + struct liftoff_output *output; + struct liftoff_layer *layer; + drmModeAtomicReq *req; + bool ok; + + mock_plane = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_PRIMARY); + + drm_fd = liftoff_mock_drm_open(); + display = liftoff_display_create(drm_fd); + assert(display != NULL); + + output = liftoff_output_create(display, liftoff_mock_drm_crtc_id); + layer = add_layer(output, 0, 0, 1920, 1080); + + req = drmModeAtomicAlloc(); + ok = liftoff_display_apply(display, req); + assert(ok); + assert(liftoff_mock_plane_get_layer(mock_plane, req) == NULL); + + liftoff_mock_plane_add_compatible_layer(mock_plane, layer); + + drmModeAtomicSetCursor(req, 0); + ok = liftoff_display_apply(display, req); + assert(ok); + assert(liftoff_mock_plane_get_layer(mock_plane, req) == layer); + drmModeAtomicFree(req); + + liftoff_display_destroy(display); + close(drm_fd); + return 0; +}