591 lines
23 KiB
C++
591 lines
23 KiB
C++
/***
|
|
|
|
Oak - Non-Linear Video Editor
|
|
Copyright (C) 2026 Oak Team
|
|
|
|
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 3 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
// Pure C ABI test for the liboakengine render/ipc facade. Exercises the
|
|
// shared-memory region, the frame slot pool (including its version-1 wire
|
|
// layout) and every control-plane message build/parse pair. No Qt, no GL.
|
|
|
|
#include <assert.h>
|
|
#include <gtest/gtest.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if defined(_WIN32)
|
|
#include <process.h>
|
|
#define getpid _getpid
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "oakengine/ipc.h"
|
|
|
|
// ---- Version-1 wire layout: oak_frame_slot_meta must never move ----------
|
|
static_assert(sizeof(oak_frame_slot_meta) == 176, "FrameSlotMeta size");
|
|
static_assert(offsetof(oak_frame_slot_meta, id) == 0, "id offset");
|
|
static_assert(offsetof(oak_frame_slot_meta, time_num) == 8, "time_num offset");
|
|
static_assert(offsetof(oak_frame_slot_meta, time_den) == 16, "time_den offset");
|
|
static_assert(offsetof(oak_frame_slot_meta, width) == 24, "width offset");
|
|
static_assert(offsetof(oak_frame_slot_meta, height) == 28, "height offset");
|
|
static_assert(offsetof(oak_frame_slot_meta, format) == 32, "format offset");
|
|
static_assert(offsetof(oak_frame_slot_meta, channel_count) == 36,
|
|
"channel_count offset");
|
|
static_assert(offsetof(oak_frame_slot_meta, linesize) == 40, "linesize offset");
|
|
static_assert(offsetof(oak_frame_slot_meta, data_size) == 44,
|
|
"data_size offset");
|
|
static_assert(offsetof(oak_frame_slot_meta, colorspace) == 48,
|
|
"colorspace offset");
|
|
|
|
static void assert_json_eq(const char *produced, const char *expected)
|
|
{
|
|
if (strcmp(produced, expected) != 0) {
|
|
fprintf(stderr, "JSON mismatch:\n produced: %s\n expected: %s\n",
|
|
produced, expected);
|
|
EXPECT_TRUE(0);
|
|
}
|
|
}
|
|
|
|
static void test_shm(void)
|
|
{
|
|
// make_key: buf/size convention and exact format
|
|
char key[OAK_IPC_SHM_KEY_CAP];
|
|
const int64_t pid = getpid();
|
|
int needed = oakengine_ipc_shm_make_key(pid, 0, NULL, 0);
|
|
char expected[64];
|
|
snprintf(expected, sizeof(expected), "olive-rw-%lld-0", (long long)pid);
|
|
EXPECT_TRUE(needed == (int)strlen(expected));
|
|
EXPECT_TRUE(oakengine_ipc_shm_make_key(pid, 0, key, sizeof(key)) == needed);
|
|
EXPECT_TRUE(strcmp(key, expected) == 0);
|
|
EXPECT_TRUE(oakengine_ipc_shm_make_key(pid, 0, key, 6) == needed); // truncated
|
|
EXPECT_TRUE(strcmp(key, "olive") == 0);
|
|
// A different worker index produces a different key.
|
|
char key1[OAK_IPC_SHM_KEY_CAP];
|
|
EXPECT_TRUE(oakengine_ipc_shm_make_key(pid, 1, key1, sizeof(key1)) == needed);
|
|
EXPECT_TRUE(strcmp(key1, expected) != 0);
|
|
|
|
const int key_len = oakengine_ipc_shm_make_key(pid, 0, key, sizeof(key));
|
|
(void)key_len;
|
|
|
|
// Attaching to a missing segment fails and reports an error.
|
|
OakSharedMemoryRegion *missing = oakengine_ipc_shm_create();
|
|
EXPECT_TRUE(oakengine_ipc_shm_open(missing, key, 4096,
|
|
OAK_IPC_SHM_MODE_ATTACH) == 0);
|
|
EXPECT_TRUE(oakengine_ipc_shm_is_valid(missing) == 0);
|
|
char err[256];
|
|
EXPECT_TRUE(oakengine_ipc_shm_error(missing, err, sizeof(err)) > 0);
|
|
EXPECT_TRUE(strlen(err) > 0);
|
|
oakengine_ipc_shm_free(missing);
|
|
|
|
// Create the owner: valid, sized, zero-initialized.
|
|
OakSharedMemoryRegion *owner = oakengine_ipc_shm_create();
|
|
EXPECT_TRUE(oakengine_ipc_shm_open(owner, key, 8192, OAK_IPC_SHM_MODE_CREATE) ==
|
|
1);
|
|
EXPECT_TRUE(oakengine_ipc_shm_is_valid(owner) == 1);
|
|
EXPECT_TRUE(oakengine_ipc_shm_size(owner) == 8192);
|
|
unsigned char *data = (unsigned char *)oakengine_ipc_shm_data(owner);
|
|
EXPECT_TRUE(data != NULL);
|
|
for (int i = 0; i < 8192; i++) {
|
|
EXPECT_TRUE(data[i] == 0);
|
|
}
|
|
|
|
// key() round-trips the opened key.
|
|
char opened_key[OAK_IPC_SHM_KEY_CAP];
|
|
EXPECT_TRUE(oakengine_ipc_shm_key(owner, opened_key, sizeof(opened_key)) ==
|
|
key_len);
|
|
EXPECT_TRUE(strcmp(opened_key, key) == 0);
|
|
|
|
// A peer attaches by the same key and sees the owner's bytes.
|
|
data[0] = 0xAB;
|
|
data[8191] = 0xCD;
|
|
OakSharedMemoryRegion *peer = oakengine_ipc_shm_create();
|
|
EXPECT_TRUE(oakengine_ipc_shm_open(peer, key, 8192, OAK_IPC_SHM_MODE_ATTACH) ==
|
|
1);
|
|
EXPECT_TRUE(oakengine_ipc_shm_size(peer) == 8192);
|
|
const unsigned char *peer_data =
|
|
(const unsigned char *)oakengine_ipc_shm_data(peer);
|
|
EXPECT_TRUE(peer_data[0] == 0xAB && peer_data[8191] == 0xCD);
|
|
peer_data = NULL;
|
|
|
|
// The peer closes without unlinking; the owner stays valid.
|
|
oakengine_ipc_shm_close(peer);
|
|
EXPECT_TRUE(oakengine_ipc_shm_is_valid(peer) == 0);
|
|
EXPECT_TRUE(oakengine_ipc_shm_is_valid(owner) == 1);
|
|
oakengine_ipc_shm_free(peer);
|
|
|
|
// The owner unlinks on close: a late attach must fail.
|
|
oakengine_ipc_shm_close(owner);
|
|
EXPECT_TRUE(oakengine_ipc_shm_is_valid(owner) == 0);
|
|
OakSharedMemoryRegion *late = oakengine_ipc_shm_create();
|
|
EXPECT_TRUE(oakengine_ipc_shm_open(late, key, 8192, OAK_IPC_SHM_MODE_ATTACH) ==
|
|
0);
|
|
oakengine_ipc_shm_free(late);
|
|
oakengine_ipc_shm_free(owner);
|
|
|
|
// NULL safety.
|
|
oakengine_ipc_shm_close(NULL);
|
|
EXPECT_TRUE(oakengine_ipc_shm_is_valid(NULL) == 0);
|
|
EXPECT_TRUE(oakengine_ipc_shm_data(NULL) == NULL);
|
|
EXPECT_TRUE(oakengine_ipc_shm_size(NULL) == 0);
|
|
oakengine_ipc_shm_free(NULL);
|
|
}
|
|
|
|
static void test_framepool(void)
|
|
{
|
|
const uint32_t k_slots = 3;
|
|
const size_t k_slot_bytes = 256;
|
|
|
|
// bytes_needed stays 64-aligned and grows with geometry.
|
|
EXPECT_TRUE(oakengine_ipc_framepool_bytes_needed(k_slots, k_slot_bytes) % 64 ==
|
|
0);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_bytes_needed(2, 65) ==
|
|
oakengine_ipc_framepool_bytes_needed(2, 128));
|
|
EXPECT_TRUE(oakengine_ipc_framepool_bytes_needed(1, 64) <
|
|
oakengine_ipc_framepool_bytes_needed(2, 64));
|
|
|
|
const size_t bytes =
|
|
oakengine_ipc_framepool_bytes_needed(k_slots, k_slot_bytes);
|
|
void *mem = malloc(bytes);
|
|
EXPECT_TRUE(mem != NULL);
|
|
|
|
OakFrameSlotPool *filler =
|
|
oakengine_ipc_framepool_create(mem, k_slots, k_slot_bytes);
|
|
EXPECT_TRUE(filler != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_is_valid(filler) == 1);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_slot_count(filler) == k_slots);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_slot_data_bytes(filler) == k_slot_bytes);
|
|
|
|
// Peer maps the same memory; a copy of the handle views it too.
|
|
OakFrameSlotPool *drainer = oakengine_ipc_framepool_attach(mem);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_is_valid(drainer) == 1);
|
|
OakFrameSlotPool *drainer_copy = oakengine_ipc_framepool_copy(drainer);
|
|
EXPECT_TRUE(drainer_copy != NULL && drainer_copy != drainer);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_slot_count(drainer_copy) == k_slots);
|
|
|
|
// Free slots are issued FIFO; the pool then reports exhausted.
|
|
uint32_t held[3];
|
|
for (uint32_t i = 0; i < k_slots; i++) {
|
|
EXPECT_TRUE(oakengine_ipc_framepool_acquire(filler, &held[i]) == 1);
|
|
EXPECT_TRUE(held[i] == i);
|
|
}
|
|
uint32_t overflow = 99;
|
|
EXPECT_TRUE(oakengine_ipc_framepool_acquire(filler, &overflow) == 0);
|
|
EXPECT_TRUE(overflow == 99);
|
|
|
|
// Fill slot 0 with a pattern + full metadata and publish it.
|
|
unsigned char *data =
|
|
(unsigned char *)oakengine_ipc_framepool_slot_data(filler, held[0]);
|
|
EXPECT_TRUE(data != NULL);
|
|
for (size_t i = 0; i < k_slot_bytes; i++) {
|
|
data[i] = (unsigned char)(i & 0xFF);
|
|
}
|
|
oak_frame_slot_meta *meta = oakengine_ipc_framepool_meta(filler, held[0]);
|
|
EXPECT_TRUE(meta != NULL);
|
|
meta->id = -4242;
|
|
meta->time_num = 1001;
|
|
meta->time_den = 30000;
|
|
meta->width = 3840;
|
|
meta->height = 2160;
|
|
meta->format = 17;
|
|
meta->channel_count = 4;
|
|
meta->linesize = 3840 * 4 * 4;
|
|
meta->data_size = (int32_t)k_slot_bytes;
|
|
strncpy(meta->colorspace, "acescg", sizeof(meta->colorspace) - 1);
|
|
meta->colorspace[sizeof(meta->colorspace) - 1] = '\0';
|
|
EXPECT_TRUE(oakengine_ipc_framepool_publish(filler, held[0]) == 1);
|
|
|
|
// Publish slot 2 then slot 1: consume order follows publish order.
|
|
EXPECT_TRUE(oakengine_ipc_framepool_publish(filler, held[2]) == 1);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_publish(filler, held[1]) == 1);
|
|
|
|
uint32_t got = 99;
|
|
EXPECT_TRUE(oakengine_ipc_framepool_consume(drainer, &got) == 1);
|
|
EXPECT_TRUE(got == held[0]);
|
|
|
|
// Metadata and pixels arrive intact on the drainer side.
|
|
const oak_frame_slot_meta *out =
|
|
oakengine_ipc_framepool_meta_const(drainer, got);
|
|
EXPECT_TRUE(out != NULL);
|
|
EXPECT_TRUE(out->id == -4242);
|
|
EXPECT_TRUE(out->time_num == 1001 && out->time_den == 30000);
|
|
EXPECT_TRUE(out->width == 3840 && out->height == 2160);
|
|
EXPECT_TRUE(out->format == 17 && out->channel_count == 4);
|
|
EXPECT_TRUE(out->linesize == 3840 * 4 * 4);
|
|
EXPECT_TRUE(out->data_size == (int32_t)k_slot_bytes);
|
|
EXPECT_TRUE(strcmp(out->colorspace, "acescg") == 0);
|
|
const unsigned char *out_data =
|
|
(const unsigned char *)oakengine_ipc_framepool_slot_data_const(drainer,
|
|
got);
|
|
EXPECT_TRUE(out_data != NULL);
|
|
for (size_t i = 0; i < k_slot_bytes; i++) {
|
|
EXPECT_TRUE(out_data[i] == (unsigned char)(i & 0xFF));
|
|
}
|
|
EXPECT_TRUE(oakengine_ipc_framepool_release(drainer, got) == 1);
|
|
|
|
// The copied handle consumes the remaining slots in publish order.
|
|
EXPECT_TRUE(oakengine_ipc_framepool_consume(drainer_copy, &got) == 1);
|
|
EXPECT_TRUE(got == held[2]);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_release(drainer_copy, got) == 1);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_consume(drainer_copy, &got) == 1);
|
|
EXPECT_TRUE(got == held[1]);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_release(drainer_copy, got) == 1);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_consume(drainer, &got) == 0); // drained
|
|
|
|
// Released slots cycle back to the filler.
|
|
uint32_t again = 0;
|
|
EXPECT_TRUE(oakengine_ipc_framepool_acquire(filler, &again) == 1);
|
|
|
|
oakengine_ipc_framepool_free(drainer_copy);
|
|
oakengine_ipc_framepool_free(drainer);
|
|
oakengine_ipc_framepool_free(filler);
|
|
free(mem);
|
|
|
|
// A region without the pool magic attaches as invalid.
|
|
void *raw = calloc(1, oakengine_ipc_framepool_bytes_needed(2, 64));
|
|
EXPECT_TRUE(raw != NULL);
|
|
OakFrameSlotPool *bad = oakengine_ipc_framepool_attach(raw);
|
|
EXPECT_TRUE(bad != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_is_valid(bad) == 0);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_slot_count(bad) == 0);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_slot_data_bytes(bad) == 0);
|
|
oakengine_ipc_framepool_free(bad);
|
|
free(raw);
|
|
|
|
// NULL safety.
|
|
EXPECT_TRUE(oakengine_ipc_framepool_is_valid(NULL) == 0);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_slot_count(NULL) == 0);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_slot_data(NULL, 0) == NULL);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_meta(NULL, 0) == NULL);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_acquire(NULL, &again) == 0);
|
|
oakengine_ipc_framepool_free(NULL);
|
|
}
|
|
|
|
// Frame hand-off across two live shared-memory mappings, the app<->worker shape.
|
|
static void test_framepool_over_shm(void)
|
|
{
|
|
char key[OAK_IPC_SHM_KEY_CAP];
|
|
EXPECT_TRUE(oakengine_ipc_shm_make_key(getpid(), 7, key, sizeof(key)) > 0);
|
|
|
|
const uint32_t k_slots = 2;
|
|
const size_t k_slot_bytes = 64;
|
|
const size_t bytes =
|
|
oakengine_ipc_framepool_bytes_needed(k_slots, k_slot_bytes);
|
|
|
|
OakSharedMemoryRegion *owner = oakengine_ipc_shm_create();
|
|
EXPECT_TRUE(oakengine_ipc_shm_open(owner, key, bytes, OAK_IPC_SHM_MODE_CREATE) ==
|
|
1);
|
|
OakSharedMemoryRegion *peer = oakengine_ipc_shm_create();
|
|
EXPECT_TRUE(oakengine_ipc_shm_open(peer, key, bytes, OAK_IPC_SHM_MODE_ATTACH) ==
|
|
1);
|
|
|
|
OakFrameSlotPool *filler = oakengine_ipc_framepool_create(
|
|
oakengine_ipc_shm_data(owner), k_slots, k_slot_bytes);
|
|
OakFrameSlotPool *drainer = oakengine_ipc_framepool_attach(
|
|
oakengine_ipc_shm_data(peer));
|
|
EXPECT_TRUE(oakengine_ipc_framepool_is_valid(filler) == 1);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_is_valid(drainer) == 1);
|
|
|
|
uint32_t idx = 0;
|
|
EXPECT_TRUE(oakengine_ipc_framepool_acquire(filler, &idx) == 1);
|
|
oak_frame_slot_meta *meta = oakengine_ipc_framepool_meta(filler, idx);
|
|
meta->id = 77;
|
|
meta->data_size = (int32_t)k_slot_bytes;
|
|
memset(oakengine_ipc_framepool_slot_data(filler, idx), 0x5A, k_slot_bytes);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_publish(filler, idx) == 1);
|
|
|
|
uint32_t got = 0;
|
|
EXPECT_TRUE(oakengine_ipc_framepool_consume(drainer, &got) == 1);
|
|
EXPECT_TRUE(got == idx);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_meta_const(drainer, got)->id == 77);
|
|
const unsigned char *d = (const unsigned char *)
|
|
oakengine_ipc_framepool_slot_data_const(drainer, got);
|
|
EXPECT_TRUE(d[0] == 0x5A && d[k_slot_bytes - 1] == 0x5A);
|
|
EXPECT_TRUE(oakengine_ipc_framepool_release(drainer, got) == 1);
|
|
|
|
oakengine_ipc_framepool_free(drainer);
|
|
oakengine_ipc_framepool_free(filler);
|
|
oakengine_ipc_shm_free(peer);
|
|
oakengine_ipc_shm_free(owner); // owner frees last: unlinks the segment
|
|
}
|
|
|
|
static void test_handshake(void)
|
|
{
|
|
oak_ipc_handshake hs;
|
|
memset(&hs, 0, sizeof(hs));
|
|
hs.protocol_version = 1;
|
|
strcpy(hs.shm_key, "olive-rw-1234-0-out");
|
|
strcpy(hs.input_shm_key, "olive-rw-1234-1-in");
|
|
hs.input_slots = 4;
|
|
hs.output_slots = 6;
|
|
hs.slot_data_bytes = 256ll * 1024 * 1024;
|
|
hs.input_slot_data_bytes = 128ll * 1024 * 1024;
|
|
|
|
const int needed = oakengine_ipc_handshake_to_json(&hs, NULL, 0);
|
|
EXPECT_TRUE(needed > 0);
|
|
char *json = (char *)malloc(size_t(needed) + 1);
|
|
EXPECT_TRUE(json != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_handshake_to_json(&hs, json, needed + 1) == needed);
|
|
EXPECT_TRUE((int)strlen(json) == needed);
|
|
|
|
// buf/size convention: a short buffer truncates but reports the full size.
|
|
char tiny[8];
|
|
EXPECT_TRUE(oakengine_ipc_handshake_to_json(&hs, tiny, sizeof(tiny)) == needed);
|
|
EXPECT_TRUE(strlen(tiny) == sizeof(tiny) - 1);
|
|
|
|
EXPECT_TRUE(oakengine_ipc_message_type(json) == OAK_IPC_MSGTYPE_HANDSHAKE);
|
|
|
|
oak_ipc_handshake back;
|
|
EXPECT_TRUE(oakengine_ipc_handshake_parse(json, &back) == 1);
|
|
EXPECT_TRUE(back.protocol_version == hs.protocol_version);
|
|
EXPECT_TRUE(strcmp(back.shm_key, hs.shm_key) == 0);
|
|
EXPECT_TRUE(strcmp(back.input_shm_key, hs.input_shm_key) == 0);
|
|
EXPECT_TRUE(back.input_slots == hs.input_slots);
|
|
EXPECT_TRUE(back.output_slots == hs.output_slots);
|
|
EXPECT_TRUE(back.slot_data_bytes == hs.slot_data_bytes);
|
|
EXPECT_TRUE(back.input_slot_data_bytes == hs.input_slot_data_bytes);
|
|
free(json);
|
|
|
|
// Exact wire text: same field names and compact shape the Qt builder emits.
|
|
oak_ipc_handshake small;
|
|
memset(&small, 0, sizeof(small));
|
|
small.protocol_version = 1;
|
|
strcpy(small.shm_key, "out");
|
|
strcpy(small.input_shm_key, "in");
|
|
small.input_slots = 4;
|
|
small.output_slots = 6;
|
|
small.slot_data_bytes = 256;
|
|
small.input_slot_data_bytes = 128;
|
|
const int n2 = oakengine_ipc_handshake_to_json(&small, NULL, 0);
|
|
char *json2 = (char *)malloc(size_t(n2) + 1);
|
|
EXPECT_TRUE(json2 != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_handshake_to_json(&small, json2, n2 + 1) == n2);
|
|
assert_json_eq(json2,
|
|
"{\"input_shm_key\":\"in\",\"input_slot_data_bytes\":128,"
|
|
"\"input_slots\":4,\"output_slots\":6,\"protocol_version\":1,"
|
|
"\"shm_key\":\"out\",\"slot_data_bytes\":256,"
|
|
"\"type\":\"handshake\"}");
|
|
free(json2);
|
|
|
|
EXPECT_TRUE(oakengine_ipc_handshake_to_json(NULL, NULL, 0) == -1);
|
|
EXPECT_TRUE(oakengine_ipc_handshake_parse("not json", &back) == 0);
|
|
EXPECT_TRUE(oakengine_ipc_handshake_parse(NULL, &back) == 0);
|
|
}
|
|
|
|
static void test_render_frame(void)
|
|
{
|
|
oak_ipc_render_frame rf;
|
|
memset(&rf, 0, sizeof(rf));
|
|
rf.ticket_id = (int64_t(1) << 52) + 12345; // exact as a JSON double
|
|
strcpy(rf.node_uuid, "{abcd-1234}");
|
|
rf.time_num = (int64_t)48000 * 123456789;
|
|
rf.time_den = int64_t(1) << 40;
|
|
rf.width = 1920;
|
|
rf.height = 1080;
|
|
rf.format = 3;
|
|
rf.channel_count = 4;
|
|
rf.mode = 1;
|
|
rf.input_slot = 2;
|
|
rf.input_slots[0] = 2;
|
|
rf.input_slots[1] = 3;
|
|
rf.input_slot_count = 2;
|
|
rf.has_color_transform = 1;
|
|
rf.color_is_display = 1;
|
|
strcpy(rf.color_output, "sRGB - Display");
|
|
strcpy(rf.color_view, "ACES 1.0 SDR-video");
|
|
strcpy(rf.color_look, "None");
|
|
|
|
const int needed = oakengine_ipc_render_frame_to_json(&rf, NULL, 0);
|
|
char *json = (char *)malloc(size_t(needed) + 1);
|
|
EXPECT_TRUE(json != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_render_frame_to_json(&rf, json, needed + 1) == needed);
|
|
EXPECT_TRUE(oakengine_ipc_message_type(json) == OAK_IPC_MSGTYPE_RENDER_FRAME);
|
|
|
|
oak_ipc_render_frame back;
|
|
EXPECT_TRUE(oakengine_ipc_render_frame_parse(json, &back) == 1);
|
|
EXPECT_TRUE(back.ticket_id == rf.ticket_id);
|
|
EXPECT_TRUE(strcmp(back.node_uuid, rf.node_uuid) == 0);
|
|
EXPECT_TRUE(back.time_num == rf.time_num);
|
|
EXPECT_TRUE(back.time_den == rf.time_den);
|
|
EXPECT_TRUE(back.width == rf.width && back.height == rf.height);
|
|
EXPECT_TRUE(back.format == rf.format && back.channel_count == rf.channel_count);
|
|
EXPECT_TRUE(back.mode == rf.mode);
|
|
EXPECT_TRUE(back.input_slot == rf.input_slot);
|
|
EXPECT_TRUE(back.input_slot_count == 2);
|
|
EXPECT_TRUE(back.input_slots[0] == 2 && back.input_slots[1] == 3);
|
|
EXPECT_TRUE(back.has_color_transform == 1 && back.color_is_display == 1);
|
|
EXPECT_TRUE(strcmp(back.color_output, rf.color_output) == 0);
|
|
EXPECT_TRUE(strcmp(back.color_view, rf.color_view) == 0);
|
|
EXPECT_TRUE(strcmp(back.color_look, rf.color_look) == 0);
|
|
free(json);
|
|
|
|
// Without a color transform the color keys are omitted from the wire text.
|
|
oak_ipc_render_frame plain;
|
|
memset(&plain, 0, sizeof(plain));
|
|
plain.ticket_id = 99;
|
|
strcpy(plain.node_uuid, "{abcd-1234}");
|
|
plain.time_num = 1001;
|
|
plain.time_den = 30000;
|
|
plain.width = 1920;
|
|
plain.height = 1080;
|
|
plain.format = 3;
|
|
plain.channel_count = 4;
|
|
plain.mode = 1;
|
|
plain.input_slot = 2;
|
|
plain.input_slots[0] = 2;
|
|
plain.input_slots[1] = 3;
|
|
plain.input_slot_count = 2;
|
|
const int n2 = oakengine_ipc_render_frame_to_json(&plain, NULL, 0);
|
|
char *json2 = (char *)malloc(size_t(n2) + 1);
|
|
EXPECT_TRUE(json2 != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_render_frame_to_json(&plain, json2, n2 + 1) == n2);
|
|
EXPECT_TRUE(strstr(json2, "has_color_transform") == NULL);
|
|
assert_json_eq(json2,
|
|
"{\"channels\":4,\"format\":3,\"height\":1080,"
|
|
"\"input_slot\":2,\"input_slots\":[2,3],\"mode\":1,"
|
|
"\"node\":\"{abcd-1234}\",\"ticket\":99,\"time_den\":30000,"
|
|
"\"time_num\":1001,\"type\":\"render_frame\",\"width\":1920}");
|
|
free(json2);
|
|
|
|
// Defaults for a sparse message, mirroring the Qt from_json defaults.
|
|
oak_ipc_render_frame sparse;
|
|
EXPECT_TRUE(oakengine_ipc_render_frame_parse("{\"type\":\"render_frame\"}",
|
|
&sparse) == 1);
|
|
EXPECT_TRUE(sparse.ticket_id == 0);
|
|
EXPECT_TRUE(sparse.time_num == 0 && sparse.time_den == 1);
|
|
EXPECT_TRUE(sparse.width == 0 && sparse.height == 0);
|
|
EXPECT_TRUE(sparse.format == -1 && sparse.channel_count == 0 && sparse.mode == 0);
|
|
EXPECT_TRUE(sparse.input_slot == -1 && sparse.input_slot_count == 0);
|
|
EXPECT_TRUE(sparse.has_color_transform == 0);
|
|
|
|
// Legacy scalar input_slot folds into the array when it is absent.
|
|
oak_ipc_render_frame legacy;
|
|
EXPECT_TRUE(oakengine_ipc_render_frame_parse(
|
|
"{\"type\":\"render_frame\",\"ticket\":5,\"input_slot\":3}",
|
|
&legacy) == 1);
|
|
EXPECT_TRUE(legacy.input_slot == 3);
|
|
EXPECT_TRUE(legacy.input_slot_count == 1 && legacy.input_slots[0] == 3);
|
|
|
|
// Type mismatches are rejected.
|
|
oak_ipc_handshake hs;
|
|
memset(&hs, 0, sizeof(hs));
|
|
const int hn = oakengine_ipc_handshake_to_json(&hs, NULL, 0);
|
|
char *hjson = (char *)malloc(size_t(hn) + 1);
|
|
EXPECT_TRUE(hjson != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_handshake_to_json(&hs, hjson, hn + 1) == hn);
|
|
EXPECT_TRUE(oakengine_ipc_render_frame_parse(hjson, &back) == 0);
|
|
EXPECT_TRUE(oakengine_ipc_handshake_parse(hjson, &hs) == 1);
|
|
free(hjson);
|
|
}
|
|
|
|
static void test_small_messages(void)
|
|
{
|
|
// frame_ready
|
|
oak_ipc_frame_ready fr;
|
|
fr.ticket_id = 99;
|
|
fr.output_slot = 2;
|
|
const int fn = oakengine_ipc_frame_ready_to_json(&fr, NULL, 0);
|
|
char *fjson = (char *)malloc(size_t(fn) + 1);
|
|
EXPECT_TRUE(fjson != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_frame_ready_to_json(&fr, fjson, fn + 1) == fn);
|
|
assert_json_eq(fjson, "{\"slot\":2,\"ticket\":99,\"type\":\"frame_ready\"}");
|
|
EXPECT_TRUE(oakengine_ipc_message_type(fjson) == OAK_IPC_MSGTYPE_FRAME_READY);
|
|
oak_ipc_frame_ready fr_back;
|
|
EXPECT_TRUE(oakengine_ipc_frame_ready_parse(fjson, &fr_back) == 1);
|
|
EXPECT_TRUE(fr_back.ticket_id == 99 && fr_back.output_slot == 2);
|
|
free(fjson);
|
|
|
|
// cancel
|
|
oak_ipc_cancel cancel;
|
|
cancel.ticket_id = 7;
|
|
const int cn = oakengine_ipc_cancel_to_json(&cancel, NULL, 0);
|
|
char *cjson = (char *)malloc(size_t(cn) + 1);
|
|
EXPECT_TRUE(cjson != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_cancel_to_json(&cancel, cjson, cn + 1) == cn);
|
|
assert_json_eq(cjson, "{\"ticket\":7,\"type\":\"cancel\"}");
|
|
EXPECT_TRUE(oakengine_ipc_message_type(cjson) == OAK_IPC_MSGTYPE_CANCEL);
|
|
oak_ipc_cancel cancel_back;
|
|
EXPECT_TRUE(oakengine_ipc_cancel_parse(cjson, &cancel_back) == 1);
|
|
EXPECT_TRUE(cancel_back.ticket_id == 7);
|
|
// cancel rejects a frame_ready payload.
|
|
EXPECT_TRUE(oakengine_ipc_cancel_parse("{\"slot\":2,\"ticket\":7,\"type\":"
|
|
"\"frame_ready\"}",
|
|
&cancel_back) == 0);
|
|
free(cjson);
|
|
|
|
// load_graph
|
|
oak_ipc_load_graph load;
|
|
strcpy(load.path, "/tmp/oak-render-graph-abc123.ove");
|
|
const int ln = oakengine_ipc_load_graph_to_json(&load, NULL, 0);
|
|
char *ljson = (char *)malloc(size_t(ln) + 1);
|
|
EXPECT_TRUE(ljson != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_load_graph_to_json(&load, ljson, ln + 1) == ln);
|
|
assert_json_eq(ljson,
|
|
"{\"path\":\"/tmp/oak-render-graph-abc123.ove\",\"type\":"
|
|
"\"load_graph\"}");
|
|
EXPECT_TRUE(oakengine_ipc_message_type(ljson) == OAK_IPC_MSGTYPE_LOAD_GRAPH);
|
|
oak_ipc_load_graph load_back;
|
|
EXPECT_TRUE(oakengine_ipc_load_graph_parse(ljson, &load_back) == 1);
|
|
EXPECT_TRUE(strcmp(load_back.path, load.path) == 0);
|
|
free(ljson);
|
|
|
|
// shutdown
|
|
const int sn = oakengine_ipc_shutdown_to_json(NULL, 0);
|
|
char *sjson = (char *)malloc(size_t(sn) + 1);
|
|
EXPECT_TRUE(sjson != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_shutdown_to_json(sjson, sn + 1) == sn);
|
|
assert_json_eq(sjson, "{\"type\":\"shutdown\"}");
|
|
EXPECT_TRUE(oakengine_ipc_shutdown_parse(sjson) == 1);
|
|
EXPECT_TRUE(oakengine_ipc_shutdown_parse("{\"type\":\"cancel\"}") == 0);
|
|
EXPECT_TRUE(oakengine_ipc_message_type(sjson) == OAK_IPC_MSGTYPE_SHUTDOWN);
|
|
free(sjson);
|
|
|
|
// error
|
|
const int en = oakengine_ipc_error_to_json("boom", NULL, 0);
|
|
char *ejson = (char *)malloc(size_t(en) + 1);
|
|
EXPECT_TRUE(ejson != NULL);
|
|
EXPECT_TRUE(oakengine_ipc_error_to_json("boom", ejson, en + 1) == en);
|
|
assert_json_eq(ejson, "{\"message\":\"boom\",\"type\":\"error\"}");
|
|
EXPECT_TRUE(oakengine_ipc_message_type(ejson) == OAK_IPC_MSGTYPE_ERROR);
|
|
char msg[OAK_IPC_ERROR_MESSAGE_CAP];
|
|
EXPECT_TRUE(oakengine_ipc_error_parse(ejson, msg, sizeof(msg)) == 1);
|
|
EXPECT_TRUE(strcmp(msg, "boom") == 0);
|
|
EXPECT_TRUE(oakengine_ipc_error_parse("{\"type\":\"shutdown\"}", msg,
|
|
sizeof(msg)) == 0);
|
|
free(ejson);
|
|
|
|
// Unknown and malformed input.
|
|
EXPECT_TRUE(oakengine_ipc_message_type("{\"type\":\"nope\"}") ==
|
|
OAK_IPC_MSGTYPE_UNKNOWN);
|
|
EXPECT_TRUE(oakengine_ipc_message_type("garbage") == OAK_IPC_MSGTYPE_UNKNOWN);
|
|
EXPECT_TRUE(oakengine_ipc_message_type(NULL) == OAK_IPC_MSGTYPE_UNKNOWN);
|
|
EXPECT_TRUE(OAK_IPC_MSGTYPE_GRAPH_UPDATE != OAK_IPC_MSGTYPE_UNKNOWN);
|
|
}
|
|
|
|
TEST(OakEngineIpc, Main)
|
|
{
|
|
test_shm();
|
|
test_framepool();
|
|
test_framepool_over_shm();
|
|
test_handshake();
|
|
test_render_frame();
|
|
test_small_messages();
|
|
|
|
}
|