mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 23:21:29 +00:00
801 lines
22 KiB
C
801 lines
22 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 LGColorTransform * 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;
|
|
}
|