Files
LookingGlass/client/tests/frame_timing_test.c
Geoffrey McRae 8fbe3e7454
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
[repos] lgprotocol: import shared protocol definitions
Add LGProtocol as a pinned submodule and consume its KVMFR protocol
definitions throughout the client, host, IDD, OBS, and profiler.

Keep Looking Glass framebuffer helpers local while removing duplicated
protocol headers and migrating users to KVMFR-scoped types.
2026-09-01 20:58:25 +10:00

801 lines
23 KiB
C

/**
* 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 "test.h"
#define main lgClientMain
#include "../src/main.c"
#undef main
#include <math.h>
#include <pthread.h>
#define TEST_ALARM_S 5U
struct Feedback
{
unsigned int count;
uint64_t serial;
uint32_t generation;
uint32_t epoch;
uint32_t deadline;
uint64_t phase;
};
static struct Feedback feed;
static unsigned int rawConnectCalls;
static unsigned int cancellableConnectCalls;
static bool connectSawCancellation;
uint64_t renderQueue_sourceBegin(RenderQueueSource source)
{
(void)source;
return 1;
}
void renderQueue_sourceInvalidate(RenderQueueSource source,
uint64_t generation)
{
(void)source;
(void)generation;
}
void renderQueue_sourceClearCursor(RenderQueueSource source)
{
(void)source;
}
bool renderQueue_sourceSwSurfaceConfigure(RenderQueueSource source,
uint64_t generation, int width, int height)
{
(void)source;
(void)generation;
(void)width;
(void)height;
return true;
}
void renderQueue_sourceSwSurfaceDrawFill(RenderQueueSource source,
uint64_t generation, int x, int y, int width, int height,
uint32_t color)
{
(void)source;
(void)generation;
(void)x;
(void)y;
(void)width;
(void)height;
(void)color;
}
void renderQueue_sourceSwSurfaceDrawBitmap(RenderQueueSource source,
uint64_t generation, int x, int y, int width, int height, int stride,
const void * data, bool topDown)
{
(void)source;
(void)generation;
(void)x;
(void)y;
(void)width;
(void)height;
(void)stride;
(void)data;
(void)topDown;
}
void renderQueue_sourceCursorState(RenderQueueSource source,
uint64_t generation, bool visible, int x, int y, int hx, int hy)
{
(void)source;
(void)generation;
(void)visible;
(void)x;
(void)y;
(void)hx;
(void)hy;
}
void renderQueue_sourceCursorImage(RenderQueueSource source,
uint64_t generation, LG_RendererCursor type,
int width, int height, int pitch, const void * data)
{
(void)source;
(void)generation;
(void)type;
(void)width;
(void)height;
(void)pitch;
(void)data;
}
void renderQueue_sourceCursorColorTransform(RenderQueueSource source,
uint64_t generation, const KVMFRColorTransform * transform)
{
(void)source;
(void)generation;
(void)transform;
}
void renderQueue_sourceCursorWhiteLevel(RenderQueueSource source,
uint64_t generation, uint32_t sdrWhiteLevel)
{
(void)source;
(void)generation;
(void)sdrWhiteLevel;
}
void overlaySplash_show(bool show)
{
(void)show;
}
bool lgTransportFallback_usable(const LG_TransportFallback * fallback)
{
(void)fallback;
return false;
}
bool lgTransportFallback_ready(const LG_TransportFallback * fallback)
{
(void)fallback;
return false;
}
bool lgTransportFallback_acceptsVideoEvents(
const LG_TransportFallback * fallback)
{
(void)fallback;
return false;
}
bool lgTransportFallback_videoRequested(LG_TransportFallback * fallback)
{
(void)fallback;
return false;
}
void lgTransportFallback_requestVideoActive(
LG_TransportFallback * fallback, bool active)
{
(void)fallback;
(void)active;
}
bool lgInput_available(void)
{
return false;
}
bool core_inputEnabled(void)
{
return false;
}
void core_alignToGuest(void) {}
void core_handleGuestMouseUpdate(void) {}
void core_setTitle(const char * title) { (void)title; }
enum RunState app_getState(void)
{
return atomic_load_explicit(&p_appState, memory_order_acquire);
}
void app_setState(enum RunState state)
{
atomic_store_explicit(&p_appState, state, memory_order_release);
}
void app_invalidateWindow(bool full)
{
(void)full;
}
void app_refreshVideoSource(void) {}
void app_updateMouseState(void) {}
static bool cancelled(void * opaque)
{
(void)opaque;
return true;
}
static LG_TransportStatus rawConnect(LG_Transport * transport,
LG_TransportSession * session)
{
(void)transport;
(void)session;
++rawConnectCalls;
return LG_TRANSPORT_ERROR;
}
static LG_TransportStatus cancellableConnect(LG_Transport * transport,
LG_TransportSession * session, LG_TransportCancelledFn cancelled,
void * opaque)
{
(void)transport;
(void)session;
++cancellableConnectCalls;
connectSawCancellation = cancelled && cancelled(opaque);
return connectSawCancellation ? LG_TRANSPORT_DISCONNECTED :
LG_TRANSPORT_OK;
}
void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation,
uint32_t scheduleEpoch, uint32_t deadlineSerial,
uint64_t measuredPhase)
{
++feed.count;
feed.serial = frameSerial;
feed.generation = generation;
feed.epoch = scheduleEpoch;
feed.deadline = deadlineSerial;
feed.phase = measuredPhase;
}
static LG_TransportFrameTiming srcTiming(bool valid, bool phase)
{
return (LG_TransportFrameTiming)
{
.valid = valid,
.providerValid = true,
.phaseValid = phase,
.scheduleGeneration = 2,
.scheduleEpoch = 3,
.scheduleDeadlineSerial = 4,
.captureTime = 11,
.postProcessTime = 12,
.copyTime = 13,
.readyTime = 14,
.holdTime = 15,
.readyLeadTime = 110,
.receiveTime = 2,
.prepareTime = 3,
};
}
static LG_RendererFrameTiming dstTiming(LG_RendererFrameToken token,
bool tracked)
{
return (LG_RendererFrameTiming)
{
.frameToken = token,
.setupTime = 21,
.effectsTime = 22,
.desktopTime = 23,
.composeTime = 24,
.swapTime = 25,
.presentTracked = tracked,
};
}
static void start(void)
{
memset(&g_state, 0, sizeof(g_state));
memset(&feed, 0, sizeof(feed));
g_state.frameLatency = ringbuffer_new(16, sizeof(OverlayFrameTiming));
CHECK(g_state.frameLatency);
frameTimingInit();
}
static void finish(void)
{
ringbuffer_free(&g_state.frameLatency);
LG_LOCK_FREE(l_frameTiming.lock);
}
static OverlayFrameTiming take(void)
{
OverlayFrameTiming out;
CHECK(ringbuffer_consume(g_state.frameLatency, &out, 1) == 1);
return out;
}
static bool near(float value, float expected)
{
return fabsf(value - expected) < 0.000001f;
}
static void queue(LG_RendererFrameToken token,
const LG_TransportFrameTiming * timing, uint64_t serial)
{
frameTimingQueue(token, serial, timing, 10, 5, 100, 150);
}
static void testLifecycle(void)
{
start();
CHECK(frameTimingQueuedToken() == LG_RENDERER_FRAME_TOKEN_NONE);
const LG_TransportFrameTiming timing = srcTiming(true, true);
const LG_RendererFrameToken first = frameTimingReserve();
CHECK(first == 1);
queue(first, &timing, 10);
CHECK(frameTimingQueuedToken() == first);
CHECK(frameTimingRecord(first)->dispatchTime == 35);
frameTimingCancel(first);
main_framePresented(first, 200, true);
CHECK(frameTimingRecord(first)->token == LG_RENDERER_FRAME_TOKEN_NONE);
const LG_RendererFrameToken second = frameTimingReserve();
CHECK(second == 2);
frameTimingReset();
CHECK(frameTimingQueuedToken() == LG_RENDERER_FRAME_TOKEN_NONE);
CHECK(frameTimingRecord(second)->token == LG_RENDERER_FRAME_TOKEN_NONE);
CHECK(frameTimingReserve() == 3);
finish();
}
static void testOrder(void)
{
start();
const LG_TransportFrameTiming timing = srcTiming(true, true);
const LG_RendererFrameToken token = frameTimingReserve();
queue(token, &timing, 55);
main_framePresented(token, 500, true);
const LG_RendererFrameTiming render = dstTiming(token, true);
frameTimingFinishRender(&render, 200, 7, 300, token);
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 0);
frameTimingFinishFrame(token, &timing);
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 1);
const OverlayFrameTiming out = take();
CHECK(out.validMask == OVERLAY_FRAME_TIMING_VALID_ALL);
CHECK(near(out.capture, 0.000011f));
CHECK(near(out.transport, 0.000010f));
CHECK(near(out.receive, 0.000002f));
CHECK(near(out.providerPrepare, 0.000003f));
CHECK(near(out.dispatch, 0.000035f));
CHECK(near(out.queue, 0.000050f));
CHECK(near(out.present, 0.000500f));
frameTimingFinishFrame(token, &timing);
main_framePresented(token, 600, true);
frameTimingFinishRender(&render, 200, 7, 300, token);
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 0);
finish();
}
static void testUntracked(void)
{
start();
const LG_TransportFrameTiming timing = srcTiming(false, true);
const LG_RendererFrameToken token = frameTimingReserve();
queue(token, &timing, 66);
frameTimingFinishFrame(token, &timing);
const LG_RendererFrameTiming render = dstTiming(token, false);
frameTimingFinishRender(&render, 200, 7, 300, token);
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 1);
const OverlayFrameTiming out = take();
CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_PRODUCER));
CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_TRANSPORT));
CHECK((out.validMask & OVERLAY_FRAME_TIMING_VALID_PROVIDER) ==
OVERLAY_FRAME_TIMING_VALID_PROVIDER);
CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_PRESENT));
CHECK(near(out.receive, 0.000002f));
CHECK(near(out.providerPrepare, 0.000003f));
CHECK(out.present == 0.0f);
finish();
}
static void testAccounting(void)
{
start();
LG_TransportFrameTiming timing = srcTiming(true, true);
timing.readyLeadTime = 1;
timing.receiveTime = UINT64_MAX;
timing.prepareTime = UINT64_MAX;
const LG_RendererFrameToken token = frameTimingReserve();
frameTimingQueue(token, 67, &timing, UINT64_MAX, UINT64_MAX, 100, 150);
frameTimingFinishFrame(token, &timing);
const LG_RendererFrameTiming render = dstTiming(token, false);
frameTimingFinishRender(&render, 200, 7, 300, token);
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 1);
const OverlayFrameTiming out = take();
CHECK(out.validMask & OVERLAY_FRAME_TIMING_VALID_TRANSPORT);
CHECK((out.validMask & OVERLAY_FRAME_TIMING_VALID_PROVIDER) ==
OVERLAY_FRAME_TIMING_VALID_PROVIDER);
CHECK(out.transport == 0.0f);
CHECK(out.import ==
(float)frameTimingAdd(UINT64_MAX, UINT64_MAX) * 1e-6f);
finish();
}
static void testProviderUnavailable(void)
{
start();
LG_TransportFrameTiming timing = srcTiming(true, true);
timing.providerValid = false;
const LG_RendererFrameToken token = frameTimingReserve();
queue(token, &timing, 68);
frameTimingFinishFrame(token, &timing);
const LG_RendererFrameTiming render = dstTiming(token, false);
frameTimingFinishRender(&render, 200, 7, 300, token);
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 1);
const OverlayFrameTiming out = take();
CHECK(out.validMask & OVERLAY_FRAME_TIMING_VALID_PRODUCER);
CHECK(out.validMask & OVERLAY_FRAME_TIMING_VALID_TRANSPORT);
CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_PROVIDER));
CHECK(near(out.transport, 0.000015f));
finish();
}
static void testTimeout(void)
{
start();
const LG_TransportFrameTiming timing = srcTiming(true, true);
const LG_RendererFrameToken token = frameTimingReserve();
queue(token, &timing, 77);
frameTimingFinishFrame(token, &timing);
const LG_RendererFrameTiming render = dstTiming(token, true);
const uint64_t now = nanotime();
frameTimingFinishRender(&render, 200, 7, now, token);
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 0);
frameTimingRecord(token)->presentDeadline = nanotime();
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 1);
const OverlayFrameTiming out = take();
CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_PRESENT));
CHECK(out.present == 0.0f);
finish();
}
static void testFifo(void)
{
start();
LG_TransportFrameTiming firstTiming = srcTiming(true, true);
LG_TransportFrameTiming secondTiming = srcTiming(true, true);
const LG_RendererFrameToken first = frameTimingReserve();
const LG_RendererFrameToken second = frameTimingReserve();
firstTiming.captureTime = 81;
secondTiming.captureTime = 82;
queue(first, &firstTiming, 81);
queue(second, &secondTiming, 82);
const LG_RendererFrameTiming r1 = dstTiming(first, false);
const LG_RendererFrameTiming r2 = dstTiming(second, false);
frameTimingFinishRender(&r1, 200, 7, 301, first);
frameTimingFinishRender(&r2, 210, 8, 302, second);
frameTimingFinishFrame(second, &secondTiming);
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 0);
frameTimingFinishFrame(first, &firstTiming);
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 2);
const OverlayFrameTiming out1 = take();
const OverlayFrameTiming out2 = take();
CHECK(near(out1.capture, 0.000081f));
CHECK(near(out2.capture, 0.000082f));
finish();
}
static void testRetire(void)
{
start();
const LG_TransportFrameTiming timing = srcTiming(true, true);
const LG_RendererFrameToken old = frameTimingReserve();
const LG_RendererFrameToken fresh = frameTimingReserve();
queue(old, &timing, 91);
queue(fresh, &timing, 92);
const LG_RendererFrameTiming render = dstTiming(fresh, false);
frameTimingFinishRender(&render, 200, 7, 300, fresh);
frameTimingFinishFrame(old, &timing);
CHECK(frameTimingRecord(old)->token == LG_RENDERER_FRAME_TOKEN_NONE);
frameTimingFinishFrame(fresh, &timing);
frameTimingPublishReady();
CHECK(ringbuffer_getCount(g_state.frameLatency) == 1);
(void)take();
finish();
}
static void testFeedback(void)
{
start();
g_state.jitRender = true;
const LG_TransportFrameTiming timing = srcTiming(true, true);
LG_RendererFrameToken token = frameTimingReserve();
queue(token, &timing, 101);
LG_RendererFrameTiming render = dstTiming(token, false);
frameTimingFinishRender(&render, 200, 7, 300, token);
CHECK(feed.count == 1);
CHECK(feed.serial == 101);
CHECK(feed.generation == 2);
CHECK(feed.epoch == 3);
CHECK(feed.deadline == 4);
CHECK(feed.phase == 50);
token = frameTimingReserve();
queue(token, &timing, 102);
render = dstTiming(token, false);
frameTimingFinishRender(&render, 200, 7, 300, token - 1);
CHECK(feed.count == 1);
LG_TransportFrameTiming invalid = timing;
invalid.phaseValid = false;
token = frameTimingReserve();
queue(token, &invalid, 103);
render = dstTiming(token, false);
frameTimingFinishRender(&render, 200, 7, 300, token);
CHECK(feed.count == 1);
invalid = timing;
invalid.scheduleEpoch = 0;
token = frameTimingReserve();
queue(token, &invalid, 104);
render = dstTiming(token, false);
frameTimingFinishRender(&render, 200, 7, 300, token);
CHECK(feed.count == 1);
token = frameTimingReserve();
queue(token, &timing, 105);
render = dstTiming(token, false);
frameTimingFinishRender(&render, 149, 7, 300, token);
CHECK(feed.count == 1);
finish();
}
static void testWrap(void)
{
start();
l_frameTiming.nextToken = UINT64_MAX - 1;
CHECK(frameTimingReserve() == UINT64_MAX);
CHECK(frameTimingReserve() == 1);
finish();
}
static void testComponentEpoch(void)
{
atomic_bool available = true;
atomic_uint_least64_t epoch = 7;
atomic_int reason = LG_TRANSPORT_OK;
CHECK(videoComponentPayloadCurrent(&available, &epoch, 7, true));
CHECK(!videoComponentPayloadCurrent(&available, &epoch, 6, true));
CHECK(videoComponentPayloadCurrent(&available, &epoch, 0, false));
const LG_VideoComponentStatus unavailable =
{
.available = false,
.epoch = 8,
.reason = LG_TRANSPORT_ERROR,
};
CHECK(videoComponentStatusChanged(
&available, &epoch, &reason, &unavailable));
CHECK(!videoComponentPayloadCurrent(&available, &epoch, 7, true));
CHECK(!videoComponentPayloadCurrent(&available, &epoch, 0, false));
CHECK(atomic_load(&reason) == LG_TRANSPORT_ERROR);
const LG_VideoComponentStatus replacement =
{
.available = true,
.epoch = 9,
.reason = LG_TRANSPORT_OK,
};
CHECK(videoComponentStatusChanged(
&available, &epoch, &reason, &replacement));
CHECK(!videoComponentPayloadCurrent(&available, &epoch, 7, true));
CHECK(videoComponentPayloadCurrent(&available, &epoch, 9, true));
}
struct PayloadGateTrace
{
const atomic_bool * available;
const atomic_uint_least64_t * epoch;
uint64_t payloadEpoch;
uint64_t sourceGeneration;
atomic_bool entered;
atomic_bool release;
atomic_bool accepted;
atomic_bool changed;
};
static void * holdPayload(void * opaque)
{
struct PayloadGateTrace * trace = opaque;
const bool accepted = videoPayloadAccept(trace->available, trace->epoch,
trace->payloadEpoch, true, trace->sourceGeneration);
atomic_store_explicit(&trace->accepted, accepted, memory_order_release);
if (!accepted)
return NULL;
atomic_store_explicit(&trace->entered, true, memory_order_release);
while (!atomic_load_explicit(&trace->release, memory_order_acquire))
usleep(1000);
videoPayloadRelease();
return NULL;
}
static void * changePayload(void * opaque)
{
struct PayloadGateTrace * trace = opaque;
LG_LOCK(g_state.videoPayloadLock);
atomic_store_explicit((atomic_bool *)trace->available,
false, memory_order_release);
atomic_store_explicit((atomic_uint_least64_t *)trace->epoch,
trace->payloadEpoch + 1, memory_order_release);
LG_UNLOCK(g_state.videoPayloadLock);
atomic_store_explicit(&trace->changed, true, memory_order_release);
return NULL;
}
static void testPayloadGate(void)
{
memset(&g_state, 0, sizeof(g_state));
LG_LOCK_INIT(g_state.videoPayloadLock);
atomic_store_explicit(
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].generation,
5, memory_order_relaxed);
atomic_bool available = true;
atomic_uint_least64_t epoch = 7;
struct PayloadGateTrace trace =
{
.available = &available,
.epoch = &epoch,
.payloadEpoch = 7,
.sourceGeneration = 5,
};
pthread_t payloadThread;
pthread_t statusThread;
CHECK(pthread_create(&payloadThread, NULL, holdPayload, &trace) == 0);
while (!atomic_load_explicit(&trace.entered, memory_order_acquire))
usleep(1000);
CHECK(pthread_create(&statusThread, NULL, changePayload, &trace) == 0);
usleep(10000);
CHECK(!atomic_load_explicit(&trace.changed, memory_order_acquire));
atomic_store_explicit(&trace.release, true, memory_order_release);
CHECK(pthread_join(payloadThread, NULL) == 0);
CHECK(pthread_join(statusThread, NULL) == 0);
CHECK(atomic_load_explicit(&trace.accepted, memory_order_acquire));
CHECK(atomic_load_explicit(&trace.changed, memory_order_acquire));
CHECK(!videoPayloadAccept(&available, &epoch, 7, true, 5));
LG_LOCK_FREE(g_state.videoPayloadLock);
}
static void testFramePayloadBookkeeping(void)
{
memset(&g_state, 0, sizeof(g_state));
LG_LOCK_INIT(g_state.videoPayloadLock);
atomic_store_explicit(&g_state.videoFrameAvailable,
true, memory_order_relaxed);
atomic_store_explicit(&g_state.videoFrameEpoch, 7, memory_order_relaxed);
atomic_store_explicit(
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].generation,
5, memory_order_relaxed);
g_state.formatValid = true;
uint64_t sourceGeneration = 4;
uint64_t frameSerial = 41;
uint32_t formatVersion = 3;
bool sourceChanged = false;
LG_TransportFrame frame =
{
.serial = 42,
.epoch = 6,
};
CHECK(!videoFramePayloadAccept(&frame, 5, true,
&sourceGeneration, &frameSerial, &formatVersion, &sourceChanged));
CHECK(sourceGeneration == 4);
CHECK(frameSerial == 41);
CHECK(formatVersion == 3);
frame.epoch = 7;
CHECK(videoFramePayloadAccept(&frame, 5, true,
&sourceGeneration, &frameSerial, &formatVersion, &sourceChanged));
CHECK(sourceChanged);
CHECK(sourceGeneration == 5);
CHECK(frameSerial == 42);
CHECK(formatVersion == 0);
videoPayloadRelease();
CHECK(!videoFramePayloadAccept(&frame, 5, true,
&sourceGeneration, &frameSerial, &formatVersion, &sourceChanged));
LG_LOCK_FREE(g_state.videoPayloadLock);
}
static void testConnectCancellation(void)
{
rawConnectCalls = 0;
cancellableConnectCalls = 0;
connectSawCancellation = false;
const LG_TransportOps ops =
{
.connect = rawConnect,
.connectCancellable = cancellableConnect,
};
struct TransportSessionProbe probe =
{
.ops = &ops,
.cancelled = cancelled,
.done = false,
};
CHECK(transportSessionProbe(&probe) == 0);
CHECK(atomic_load_explicit(&probe.done, memory_order_acquire));
CHECK(probe.status == LG_TRANSPORT_DISCONNECTED);
CHECK(rawConnectCalls == 0);
CHECK(cancellableConnectCalls == 1);
CHECK(connectSawCancellation);
}
static void testRecoveryErrors(void)
{
CHECK(strcmp(recoveryErrorText(LG_RECOVERY_ERR_BUSY),
"A conflicting recovery request is already in progress") == 0);
CHECK(strcmp(recoveryErrorText(LG_RECOVERY_ERR_CAPACITY),
"Too many recovery requests are pending") == 0);
}
struct Test
{
const char * name;
void (*run)(void);
};
static const struct Test tests[] =
{
{ "lifecycle", testLifecycle },
{ "order" , testOrder },
{ "untracked", testUntracked },
{ "accounting", testAccounting },
{ "provider-unavailable", testProviderUnavailable },
{ "timeout" , testTimeout },
{ "fifo" , testFifo },
{ "retire" , testRetire },
{ "feedback" , testFeedback },
{ "wrap" , testWrap },
{ "component-epoch", testComponentEpoch },
{ "payload-gate", testPayloadGate },
{ "frame-payload-bookkeeping", testFramePayloadBookkeeping },
{ "connect-cancel", testConnectCancellation },
{ "recovery-errors", testRecoveryErrors },
};
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 (size_t i = 0; i < ARRAY_LENGTH(tests); ++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;
}