From 49fa27b57326ff32db0d21d44defe2887d7823e1 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 12 Aug 2026 03:43:06 +1000 Subject: [PATCH] [client] lgmp: test frame validation and leases --- client/tests/CMakeLists.txt | 26 ++ client/tests/lgmp_frame_test.c | 650 +++++++++++++++++++++++++++++++++ 2 files changed, 676 insertions(+) create mode 100644 client/tests/lgmp_frame_test.c diff --git a/client/tests/CMakeLists.txt b/client/tests/CMakeLists.txt index 2dca6432..e0cebc6c 100644 --- a/client/tests/CMakeLists.txt +++ b/client/tests/CMakeLists.txt @@ -34,6 +34,32 @@ set_tests_properties(lgmp-transport-tests PROPERTIES TIMEOUT 10 ) +add_executable(lgmp-frame-tests + lgmp_frame_test.c + ../src/kb_hid.c +) +target_link_libraries(lgmp-frame-tests + ${EXE_FLAGS} + transport_LGMP + lg_common + lgmp +) +set(LGMP_FRAME_CASES + malformed + serial-damage + lanes + leases + dma +) +foreach(name IN LISTS LGMP_FRAME_CASES) + add_test(NAME lgmp-frame-${name} + COMMAND lgmp-frame-tests ${name} + ) + set_tests_properties(lgmp-frame-${name} PROPERTIES + TIMEOUT 10 + ) +endforeach() + add_executable(wayland-motion-tests motion_test.c ) diff --git a/client/tests/lgmp_frame_test.c b/client/tests/lgmp_frame_test.c new file mode 100644 index 00000000..a04fc78f --- /dev/null +++ b/client/tests/lgmp_frame_test.c @@ -0,0 +1,650 @@ +/** + * 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 "interface/transport.h" + +#include "common/KVMFR.h" +#include "common/LGMPConfig.h" +#include "common/debug.h" +#include "common/option.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define TEST_SHM_SIZE (2U * 1024U * 1024U) +#define TEST_QUEUE_TIMEOUT 250U +#define TEST_WAIT_LOOPS 500U +#define TEST_FRAME_DATA 64U +#define TEST_MAX_ALLOCS 64U +#define TEST_FRAME_QUEUES (LGMP_Q_FRAME_LEN + 1U) + +extern const LG_TransportOps LGT_LGMP; + +typedef struct TestFrame +{ + PLGMPMemory memory; + KVMFRFrame * wire; +} +TestFrame; + +typedef struct TestState +{ + int fd; + char path[64]; + bool pathExists; + void * hostMemory; + PLGMPHost host; + PLGMPHostQueue queues[TEST_FRAME_QUEUES]; + PLGMPMemory allocations[TEST_MAX_ALLOCS]; + unsigned allocationCount; + LG_Transport * transport; + const LG_FrameOps * frameOps; +} +TestState; + +#define CHECK(x) \ + do \ + { \ + if (!(x)) \ + { \ + fprintf(stderr, "check failed at %s:%d: %s\n", \ + __FILE__, __LINE__, #x); \ + return false; \ + } \ + } \ + while (0) + +static bool waitForEmpty(TestState * state) +{ + for (unsigned attempt = 0; attempt < TEST_WAIT_LOOPS; ++attempt) + { + if (lgmpHostProcess(state->host) != LGMP_OK) + return false; + + bool empty = true; + for (unsigned i = 0; i < TEST_FRAME_QUEUES; ++i) + empty &= lgmpHostQueuePending(state->queues[i]) == 0; + if (empty) + return true; + + usleep(1000); + } + + return false; +} + +static bool allocMemory(TestState * state, uint32_t size, + PLGMPMemory * memory, void ** pointer) +{ + CHECK(state->allocationCount < TEST_MAX_ALLOCS); + CHECK(lgmpHostMemAlloc(state->host, size, memory) == LGMP_OK); + state->allocations[state->allocationCount++] = *memory; + *pointer = lgmpHostMemPtr(*memory); + return true; +} + +static bool newFrame(TestState * state, uint32_t serial, TestFrame * result) +{ + const uint32_t size = sizeof(KVMFRFrame) + sizeof(FrameBuffer) + + TEST_FRAME_DATA; + void * pointer; + CHECK(allocMemory(state, size, &result->memory, &pointer)); + + result->wire = pointer; + result->wire->formatVer = 1; + result->wire->frameSerial = serial; + result->wire->type = FRAME_TYPE_BGRA; + result->wire->screenWidth = 4; + result->wire->screenHeight = 4; + result->wire->dataWidth = 4; + result->wire->dataHeight = 4; + result->wire->frameWidth = 4; + result->wire->frameHeight = 4; + result->wire->rotation = FRAME_ROT_0; + result->wire->stride = 4; + result->wire->pitch = 16; + result->wire->offset = sizeof(KVMFRFrame); + result->wire->sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; + + FrameBuffer * framebuffer = (FrameBuffer *)((uint8_t *)result->wire + + result->wire->offset); + atomic_store(&framebuffer->wp, TEST_FRAME_DATA); + return true; +} + +static void setDamage(TestFrame * frame, uint32_t count) +{ + frame->wire->damageRectsCount = count; + for (uint32_t i = 0; i < count && i < KVMFR_MAX_DAMAGE_RECTS; ++i) + { + frame->wire->damageRects[i].x = i + 1; + frame->wire->damageRects[i].y = i + 2; + frame->wire->damageRects[i].width = i + 3; + frame->wire->damageRects[i].height = i + 4; + } +} + +static bool postShared(TestState * state, const TestFrame * frame) +{ + CHECK(lgmpHostQueuePost(state->queues[0], 0, frame->memory) == LGMP_OK); + return true; +} + +static bool postOwner(TestState * state, unsigned lane, uint64_t token, + const TestFrame * frame) +{ + CHECK(lane < LGMP_Q_FRAME_LEN); + + uint32_t clientIDs[32]; + unsigned clientCount; + unsigned recipientCount; + PLGMPHostQueue queue = state->queues[lane + 1]; + CHECK(lgmpHostGetClientIDs(queue, clientIDs, &clientCount) == LGMP_OK); + CHECK(clientCount == 1); + CHECK(lgmpHostQueuePostForClients(queue, token, frame->memory, + clientIDs, clientCount, &recipientCount) == LGMP_OK); + CHECK(recipientCount == 1); + return true; +} + +static bool resetFrames(TestState * state) +{ + state->frameOps->stopFrame(state->transport); + CHECK(waitForEmpty(state)); + + LG_TransportFrame frame; + CHECK(state->frameOps->nextFrame(state->transport, false, &frame) == + LG_TRANSPORT_TIMEOUT); + for (unsigned i = 0; i < TEST_FRAME_QUEUES; ++i) + CHECK(lgmpHostQueueHasSubs(state->queues[i])); + return true; +} + +static bool releaseFrame(TestState * state, LG_TransportFrame * frame) +{ + state->frameOps->releaseFrame(state->transport, frame); + CHECK(frame->releaseFn == NULL); + CHECK(frame->releaseHandle == 0); + CHECK(waitForEmpty(state)); + return true; +} + +static bool testMalformed(TestState * state) +{ + CHECK(resetFrames(state)); + + PLGMPMemory shortMemory; + void * shortPointer; + CHECK(allocMemory(state, sizeof(KVMFRFrame) - 4, &shortMemory, + &shortPointer)); + CHECK(lgmpHostQueuePost(state->queues[0], 0, shortMemory) == LGMP_OK); + LG_TransportFrame result; + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_ERROR); + CHECK(waitForEmpty(state)); + + TestFrame invalidType; + CHECK(newFrame(state, 1, &invalidType)); + invalidType.wire->type = FRAME_TYPE_INVALID; + CHECK(postShared(state, &invalidType)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_ERROR); + CHECK(waitForEmpty(state)); + + TestFrame invalidOffset; + CHECK(newFrame(state, 2, &invalidOffset)); + invalidOffset.wire->offset = UINT32_MAX; + CHECK(postShared(state, &invalidOffset)); + CHECK(state->frameOps->nextFrame(state->transport, true, &result) == + LG_TRANSPORT_ERROR); + CHECK(waitForEmpty(state)); + + TestFrame invalidSize; + CHECK(newFrame(state, 3, &invalidSize)); + invalidSize.wire->dataHeight = UINT32_MAX; + invalidSize.wire->pitch = UINT32_MAX; + CHECK(postShared(state, &invalidSize)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_ERROR); + CHECK(waitForEmpty(state)); + + TestFrame malformed; + TestFrame valid; + CHECK(newFrame(state, 4, &malformed)); + CHECK(newFrame(state, 4, &valid)); + malformed.wire->offset = UINT32_MAX; + valid.wire->formatVer = 44; + valid.wire->scheduleEpoch = 2; + valid.wire->scheduleDeadlineSerial = 9; + CHECK(postShared(state, &malformed)); + CHECK(postOwner(state, 0, (UINT64_C(2) << 32) | 9, &valid)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.serial == 4); + CHECK(result.scheduleOwner); + CHECK(result.format && result.format->version == 44); + CHECK(releaseFrame(state, &result)); + return true; +} + +static bool testSerialDamage(TestState * state) +{ + CHECK(resetFrames(state)); + + LG_TransportFrame result; + TestFrame first; + CHECK(newFrame(state, UINT32_MAX - 1U, &first)); + setDamage(&first, 2); + CHECK(postShared(state, &first)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.serial == UINT32_MAX - 1U); + CHECK(result.damageRects == NULL); + CHECK(result.damageRectsCount == 0); + CHECK(releaseFrame(state, &result)); + + TestFrame last; + CHECK(newFrame(state, UINT32_MAX, &last)); + setDamage(&last, 2); + last.wire->flags = FRAME_FLAG_BLOCK_SCREENSAVER | + FRAME_FLAG_REQUEST_ACTIVATION | FRAME_FLAG_TRUNCATED; + CHECK(postShared(state, &last)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.serial == UINT32_MAX); + CHECK(result.damageRectsCount == 2); + CHECK(result.damageRects); + CHECK(result.damageRects[1].x == 2); + CHECK(result.damageRects[1].height == 5); + CHECK(result.flags == (LG_TRANSPORT_FRAME_BLOCK_SCREENSAVER | + LG_TRANSPORT_FRAME_REQUEST_ACTIVATION | + LG_TRANSPORT_FRAME_TRUNCATED)); + CHECK(releaseFrame(state, &result)); + + TestFrame wrapped; + CHECK(newFrame(state, 0, &wrapped)); + setDamage(&wrapped, 1); + CHECK(postShared(state, &wrapped)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.serial == 0); + CHECK(result.damageRectsCount == 1); + CHECK(releaseFrame(state, &result)); + + TestFrame stale; + CHECK(newFrame(state, UINT32_MAX, &stale)); + CHECK(postShared(state, &stale)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_TIMEOUT); + CHECK(waitForEmpty(state)); + + TestFrame duplicate; + CHECK(newFrame(state, 0, &duplicate)); + CHECK(postShared(state, &duplicate)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_TIMEOUT); + CHECK(waitForEmpty(state)); + + TestFrame gap; + CHECK(newFrame(state, 2, &gap)); + setDamage(&gap, 2); + CHECK(postShared(state, &gap)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.damageRects == NULL); + CHECK(result.damageRectsCount == 0); + CHECK(releaseFrame(state, &result)); + + TestFrame badDamage; + CHECK(newFrame(state, 3, &badDamage)); + setDamage(&badDamage, KVMFR_MAX_DAMAGE_RECTS + 1U); + CHECK(postShared(state, &badDamage)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.damageRects == NULL); + CHECK(result.damageRectsCount == 0); + CHECK(releaseFrame(state, &result)); + return true; +} + +static bool testLaneSelection(TestState * state) +{ + CHECK(resetFrames(state)); + + LG_TransportFrame result; + TestFrame shared; + TestFrame owner; + CHECK(newFrame(state, 10, &shared)); + CHECK(newFrame(state, 9, &owner)); + shared.wire->formatVer = 10; + owner.wire->formatVer = 9; + CHECK(postShared(state, &shared)); + CHECK(postOwner(state, 0, 1, &owner)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.serial == 10); + CHECK(!result.scheduleOwner); + CHECK(result.format && result.format->version == 10); + CHECK(releaseFrame(state, &result)); + + TestFrame tiedShared; + TestFrame tiedOwner; + CHECK(newFrame(state, 11, &tiedShared)); + CHECK(newFrame(state, 11, &tiedOwner)); + tiedShared.wire->formatVer = 11; + tiedOwner.wire->formatVer = 12; + CHECK(postShared(state, &tiedShared)); + CHECK(postOwner(state, 0, (UINT64_C(5) << 32) | 7, &tiedOwner)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.serial == 11); + CHECK(result.scheduleOwner); + CHECK(result.scheduleEpoch == 5); + CHECK(result.scheduleDeadlineSerial == 7); + CHECK(result.format && result.format->version == 12); + CHECK(releaseFrame(state, &result)); + + TestFrame wrongDeadline; + TestFrame matchingDeadline; + CHECK(newFrame(state, 12, &wrongDeadline)); + CHECK(newFrame(state, 12, &matchingDeadline)); + wrongDeadline.wire->formatVer = 21; + wrongDeadline.wire->scheduleEpoch = 7; + wrongDeadline.wire->scheduleDeadlineSerial = 9; + matchingDeadline.wire->formatVer = 22; + matchingDeadline.wire->scheduleEpoch = 7; + matchingDeadline.wire->scheduleDeadlineSerial = 9; + CHECK(postOwner(state, 0, (UINT64_C(6) << 32) | 9, &wrongDeadline)); + CHECK(postOwner(state, 1, (UINT64_C(7) << 32) | 9, &matchingDeadline)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.serial == 12); + CHECK(result.scheduleEpoch == 7); + CHECK(result.scheduleDeadlineSerial == 9); + CHECK(result.format && result.format->version == 22); + CHECK(releaseFrame(state, &result)); + + TestFrame newerShared; + TestFrame olderOwner; + CHECK(newFrame(state, 13, &newerShared)); + CHECK(newFrame(state, 12, &olderOwner)); + newerShared.wire->formatVer = 31; + olderOwner.wire->formatVer = 32; + CHECK(postShared(state, &newerShared)); + CHECK(postOwner(state, 0, 10, &olderOwner)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.serial == 13); + CHECK(!result.scheduleOwner); + CHECK(result.format && result.format->version == 31); + CHECK(releaseFrame(state, &result)); + + TestFrame sharedOld; + TestFrame ownerNew; + CHECK(newFrame(state, 14, &sharedOld)); + CHECK(newFrame(state, 15, &ownerNew)); + sharedOld.wire->formatVer = 41; + ownerNew.wire->formatVer = 42; + CHECK(postShared(state, &sharedOld)); + CHECK(postOwner(state, 0, 11, &ownerNew)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.serial == 15); + CHECK(result.scheduleOwner); + CHECK(result.format && result.format->version == 42); + CHECK(releaseFrame(state, &result)); + + TestFrame duplicateShared; + CHECK(newFrame(state, 15, &duplicateShared)); + CHECK(postShared(state, &duplicateShared)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_TIMEOUT); + CHECK(waitForEmpty(state)); + + TestFrame duplicateOwner; + CHECK(newFrame(state, 15, &duplicateOwner)); + setDamage(&duplicateOwner, 1); + CHECK(postOwner(state, 0, 12, &duplicateOwner)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.serial == 15); + CHECK(result.scheduleOwner); + CHECK(result.damageRects == NULL); + CHECK(result.damageRectsCount == 0); + CHECK(releaseFrame(state, &result)); + return true; +} + +static bool testLeases(TestState * state) +{ + CHECK(resetFrames(state)); + + TestFrame first; + TestFrame second; + LG_TransportFrame firstResult; + LG_TransportFrame secondResult; + CHECK(newFrame(state, 30, &first)); + CHECK(newFrame(state, 31, &second)); + CHECK(postShared(state, &first)); + CHECK(state->frameOps->nextFrame(state->transport, false, &firstResult) == + LG_TRANSPORT_OK); + CHECK(firstResult.releaseFn); + CHECK(firstResult.releaseHandle != 0); + CHECK(lgmpHostQueueMessagePending(state->queues[0], first.memory, 0)); + + CHECK(postShared(state, &second)); + CHECK(state->frameOps->nextFrame(state->transport, false, &secondResult) == + LG_TRANSPORT_TIMEOUT); + CHECK(lgmpHostQueueMessagePending(state->queues[0], second.memory, 0)); + + firstResult.releaseFn(firstResult.releaseOpaque, firstResult.releaseHandle); + firstResult.releaseFn(firstResult.releaseOpaque, firstResult.releaseHandle); + CHECK(state->frameOps->nextFrame(state->transport, false, &secondResult) == + LG_TRANSPORT_OK); + CHECK(secondResult.serial == 31); + CHECK(!lgmpHostQueueMessagePending(state->queues[0], first.memory, 0)); + state->frameOps->releaseFrame(state->transport, &firstResult); + CHECK(releaseFrame(state, &secondResult)); + + TestFrame stopped; + LG_TransportFrame stoppedResult; + CHECK(newFrame(state, 32, &stopped)); + CHECK(postShared(state, &stopped)); + CHECK(state->frameOps->nextFrame(state->transport, false, &stoppedResult) == + LG_TRANSPORT_OK); + CHECK(stoppedResult.releaseFn); + state->frameOps->stopFrame(state->transport); + for (unsigned i = 0; i < TEST_FRAME_QUEUES; ++i) + CHECK(!lgmpHostQueueHasSubs(state->queues[i])); + stoppedResult.releaseFn(stoppedResult.releaseOpaque, + stoppedResult.releaseHandle); + stoppedResult.releaseFn(stoppedResult.releaseOpaque, + stoppedResult.releaseHandle); + state->frameOps->releaseFrame(state->transport, &stoppedResult); + CHECK(waitForEmpty(state)); + + CHECK(resetFrames(state)); + TestFrame resent; + CHECK(newFrame(state, 32, &resent)); + CHECK(postShared(state, &resent)); + CHECK(state->frameOps->nextFrame(state->transport, false, &secondResult) == + LG_TRANSPORT_OK); + CHECK(secondResult.serial == 32); + CHECK(releaseFrame(state, &secondResult)); + + TestFrame disconnected; + LG_TransportFrame disconnectedResult; + CHECK(newFrame(state, 33, &disconnected)); + CHECK(postShared(state, &disconnected)); + CHECK(state->frameOps->nextFrame(state->transport, false, + &disconnectedResult) == LG_TRANSPORT_OK); + CHECK(disconnectedResult.releaseFn); + LGT_LGMP.disconnect(state->transport); + CHECK(!LGT_LGMP.sessionValid(state->transport)); + disconnectedResult.releaseFn(disconnectedResult.releaseOpaque, + disconnectedResult.releaseHandle); + state->frameOps->releaseFrame(state->transport, &disconnectedResult); + CHECK(waitForEmpty(state)); + return true; +} + +static bool testDMAUnavailable(TestState * state) +{ + CHECK(resetFrames(state)); + CHECK(!state->frameOps->supportsDMA(state->transport)); + + TestFrame frame; + LG_TransportFrame result; + CHECK(newFrame(state, 50, &frame)); + CHECK(postShared(state, &frame)); + CHECK(state->frameOps->nextFrame(state->transport, false, &result) == + LG_TRANSPORT_OK); + CHECK(result.dmaFD == -1); + CHECK(releaseFrame(state, &result)); + return true; +} + +static bool stateInit(TestState * state) +{ + memset(state, 0, sizeof(*state)); + state->fd = -1; + state->hostMemory = MAP_FAILED; + memcpy(state->path, "/tmp/lgmp-frame-test-XXXXXX", + sizeof("/tmp/lgmp-frame-test-XXXXXX")); + + state->fd = mkstemp(state->path); + CHECK(state->fd >= 0); + state->pathExists = true; + CHECK(ftruncate(state->fd, TEST_SHM_SIZE) == 0); + state->hostMemory = mmap(NULL, TEST_SHM_SIZE, + PROT_READ | PROT_WRITE, MAP_SHARED, state->fd, 0); + CHECK(state->hostMemory != MAP_FAILED); + + KVMFR session = { 0 }; + memcpy(session.magic, KVMFR_MAGIC, sizeof(session.magic)); + session.version = KVMFR_VERSION; + session.features = KVMFR_FEATURE_FRAME_SCHEDULE; + memcpy(session.hostver, "frame-test", sizeof("frame-test")); + CHECK(lgmpHostInit(state->hostMemory, TEST_SHM_SIZE, &state->host, + sizeof(session), (uint8_t *)&session) == LGMP_OK); + + for (unsigned i = 0; i < TEST_FRAME_QUEUES; ++i) + { + const struct LGMPQueueConfig config = { + .queueID = i == 0 ? LGMP_Q_FRAME : LGMP_Q_FRAME_OWNER + i - 1, + .numMessages = LGMP_Q_FRAME_LEN, + .subTimeout = TEST_QUEUE_TIMEOUT, + }; + CHECK(lgmpHostQueueNew(state->host, config, &state->queues[i]) == + LGMP_OK); + } + + LGT_LGMP.setup(); + option_set_string("lgmp", "shmDevice", state->path); + option_set_bool("lgmp", "allowDMA", true); + option_set_int("lgmp", "framePollInterval", 0); + option_set_int("lgmp", "cursorPollInterval", 0); + CHECK(LGT_LGMP.create(&state->transport)); + + CHECK(unlink(state->path) == 0); + state->pathExists = false; + usleep(300000); + CHECK(lgmpHostProcess(state->host) == LGMP_OK); + + LG_TransportSession transportSession; + CHECK(LGT_LGMP.connect(state->transport, &transportSession) == + LG_TRANSPORT_OK); + CHECK(transportSession.features & LG_TRANSPORT_FEATURE_FRAME_SCHEDULE); + const LG_VideoOps * videoOps = LGT_LGMP.getVideoOps(state->transport); + CHECK(videoOps && videoOps->type == LG_VIDEO_TYPE_FRAME && videoOps->frame); + state->frameOps = videoOps->frame; + CHECK(state->frameOps->stopFrame); + return true; +} + +static void stateFree(TestState * state) +{ + if (state->transport) + LGT_LGMP.destroy(&state->transport); + option_free(); + for (unsigned i = 0; i < state->allocationCount; ++i) + lgmpHostMemFree(&state->allocations[i]); + lgmpHostFree(&state->host); + if (state->hostMemory != MAP_FAILED) + munmap(state->hostMemory, TEST_SHM_SIZE); + if (state->fd >= 0) + close(state->fd); + if (state->pathExists) + unlink(state->path); +} + +typedef bool (*TestFn)(TestState * state); + +static const struct +{ + const char * name; + TestFn run; +} +tests[] = +{ + { "malformed" , testMalformed }, + { "serial-damage", testSerialDamage }, + { "lanes" , testLaneSelection }, + { "leases" , testLeases }, + { "dma" , testDMAUnavailable }, +}; + +int main(int argc, char * argv[]) +{ + if (argc != 2) + { + fprintf(stderr, "usage: %s \n", + argv[0]); + return 2; + } + + TestFn test = NULL; + for (unsigned i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) + if (strcmp(argv[1], tests[i].name) == 0) + { + test = tests[i].run; + break; + } + if (!test) + { + fprintf(stderr, "unknown test case: %s\n", argv[1]); + return 2; + } + + debug_init(); + TestState state; + const bool initialized = stateInit(&state); + const bool passed = initialized && test(&state); + + stateFree(&state); + return passed ? 0 : 1; +}