mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-06 18:59:52 +00:00
244 lines
9.6 KiB
C++
244 lines
9.6 KiB
C++
#include "../../lib/tdeck_ui/UI/LXMF/CallLinkOwnership.h"
|
|
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <functional>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
|
|
using UI::LXMF::CallLinkOwnership;
|
|
|
|
static int g_pass = 0;
|
|
static int g_fail = 0;
|
|
|
|
#define EXPECT_EQ(actual, expected) \
|
|
do { \
|
|
auto _a = (actual); \
|
|
auto _e = (expected); \
|
|
if (!(_a == _e)) { \
|
|
char buf[256]; \
|
|
std::snprintf(buf, sizeof(buf), "%s:%d: %s != %s", \
|
|
__FILE__, __LINE__, #actual, #expected); \
|
|
throw std::runtime_error(buf); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define EXPECT_TRUE(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
char buf[256]; \
|
|
std::snprintf(buf, sizeof(buf), "%s:%d: expected %s", \
|
|
__FILE__, __LINE__, #cond); \
|
|
throw std::runtime_error(buf); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define RUN(name) \
|
|
do { \
|
|
try { \
|
|
name(); \
|
|
++g_pass; \
|
|
std::printf("PASS %s\n", #name); \
|
|
} catch (const std::exception& e) { \
|
|
++g_fail; \
|
|
std::printf("FAIL %s: %s\n", #name, e.what()); \
|
|
} \
|
|
} while (0)
|
|
|
|
static CallLinkOwnership::LinkId id(uint8_t first, uint8_t last) {
|
|
CallLinkOwnership::LinkId result{};
|
|
for (size_t i = 0; i < result.size(); ++i) {
|
|
result[i] = static_cast<uint8_t>(first + i);
|
|
}
|
|
result.back() = last;
|
|
return result;
|
|
}
|
|
|
|
static void replacement_rejects_all_stale_id_generation_pairs() {
|
|
CallLinkOwnership ownership;
|
|
const auto a = id(0x10, 0xa1);
|
|
const auto b = id(0x10, 0xb2); // differs only in the final ID byte
|
|
|
|
EXPECT_TRUE(ownership.publish(1, a));
|
|
EXPECT_TRUE(ownership.owns(1, a));
|
|
EXPECT_EQ(ownership.generationFor(a), 1u);
|
|
|
|
EXPECT_TRUE(ownership.publish(2, b));
|
|
EXPECT_TRUE(!ownership.owns(1, a));
|
|
EXPECT_TRUE(!ownership.owns(1, b));
|
|
EXPECT_TRUE(!ownership.owns(2, a));
|
|
EXPECT_TRUE(ownership.owns(2, b));
|
|
EXPECT_EQ(ownership.generationFor(a), 0u);
|
|
EXPECT_EQ(ownership.generationFor(b), 2u);
|
|
}
|
|
|
|
static void distinct_non_null_synthetic_links_require_exact_full_id() {
|
|
CallLinkOwnership ownership;
|
|
const auto a = id(0x40, 0xee);
|
|
auto b = a;
|
|
b[15] ^= 1u;
|
|
|
|
// Both IDs model links whose bool conversion is true. Their exact 128-bit
|
|
// identities, not that shared truth value or a truncated prefix, decide.
|
|
EXPECT_TRUE(ownership.publish(7, a));
|
|
EXPECT_TRUE(ownership.owns(7, a));
|
|
EXPECT_TRUE(!ownership.owns(7, b));
|
|
}
|
|
|
|
static void stale_close_cannot_set_or_overwrite_new_owner_slot() {
|
|
CallLinkOwnership ownership;
|
|
const auto a = id(0x20, 0xa1);
|
|
const auto b = id(0x30, 0xb2);
|
|
|
|
EXPECT_TRUE(ownership.publish(1, a));
|
|
EXPECT_TRUE(ownership.owns(1, a)); // old callback validated, then paused
|
|
EXPECT_TRUE(ownership.publish(2, b));
|
|
EXPECT_TRUE(!ownership.markClosed(1, a));
|
|
EXPECT_EQ(ownership.takeClosed(), 0u);
|
|
|
|
EXPECT_TRUE(ownership.markClosed(2, b));
|
|
EXPECT_TRUE(!ownership.markClosed(1, a));
|
|
EXPECT_EQ(ownership.takeClosed(), 2u);
|
|
EXPECT_EQ(ownership.takeClosed(), 0u);
|
|
}
|
|
|
|
static void current_close_survives_stale_attempts_before_and_after() {
|
|
CallLinkOwnership ownership;
|
|
const auto a = id(0x51, 0xa1);
|
|
const auto b = id(0x61, 0xb2);
|
|
|
|
EXPECT_TRUE(ownership.publish(1, a));
|
|
EXPECT_TRUE(ownership.publish(2, b));
|
|
EXPECT_TRUE(!ownership.markClosed(1, a));
|
|
EXPECT_TRUE(ownership.markClosed(2, b));
|
|
EXPECT_TRUE(!ownership.markClosed(1, a));
|
|
EXPECT_EQ(ownership.takeClosed(), 2u);
|
|
}
|
|
|
|
static void stale_clear_cannot_detach_or_clear_new_owner_close() {
|
|
CallLinkOwnership ownership;
|
|
const auto a = id(0x71, 0xa1);
|
|
const auto b = id(0x81, 0xb2);
|
|
|
|
EXPECT_TRUE(ownership.publish(1, a));
|
|
EXPECT_TRUE(ownership.clear(1));
|
|
EXPECT_TRUE(ownership.publish(2, b));
|
|
EXPECT_TRUE(ownership.markClosed(2, b));
|
|
EXPECT_TRUE(!ownership.clear(1));
|
|
EXPECT_TRUE(ownership.owns(2, b));
|
|
EXPECT_EQ(ownership.takeClosed(), 2u);
|
|
}
|
|
|
|
static void concurrent_stale_and_current_close_publication_is_exact() {
|
|
constexpr int kIterations = 1000;
|
|
const auto a = id(0x91, 0xa1);
|
|
const auto b = id(0xa1, 0xb2);
|
|
|
|
for (int iteration = 0; iteration < kIterations; ++iteration) {
|
|
CallLinkOwnership ownership;
|
|
EXPECT_TRUE(ownership.publish(1, a));
|
|
EXPECT_TRUE(ownership.publish(2, b));
|
|
|
|
std::atomic<unsigned> ready{0};
|
|
std::atomic<bool> start{false};
|
|
bool stale_result = true;
|
|
bool current_result = false;
|
|
auto mark = [&](uint32_t generation,
|
|
const CallLinkOwnership::LinkId& link_id,
|
|
bool& result) {
|
|
ready.fetch_add(1, std::memory_order_release);
|
|
while (!start.load(std::memory_order_acquire)) {
|
|
std::this_thread::yield();
|
|
}
|
|
result = ownership.markClosed(generation, link_id);
|
|
};
|
|
|
|
std::thread stale(mark, 1u, std::cref(a), std::ref(stale_result));
|
|
std::thread current(mark, 2u, std::cref(b), std::ref(current_result));
|
|
while (ready.load(std::memory_order_acquire) != 2) {
|
|
std::this_thread::yield();
|
|
}
|
|
start.store(true, std::memory_order_release);
|
|
stale.join();
|
|
current.join();
|
|
|
|
EXPECT_TRUE(!stale_result);
|
|
EXPECT_TRUE(current_result);
|
|
EXPECT_EQ(ownership.takeClosed(), 2u);
|
|
}
|
|
}
|
|
|
|
static void invalid_generations_are_never_published() {
|
|
CallLinkOwnership ownership;
|
|
const auto a = id(0xb1, 0xc2);
|
|
EXPECT_TRUE(!ownership.publish(0, a));
|
|
EXPECT_TRUE(!ownership.publish(CallLinkOwnership::MAX_GENERATION + 1u, a));
|
|
EXPECT_EQ(ownership.generationFor(a), 0u);
|
|
}
|
|
|
|
static void concurrent_publication_never_accepts_mixed_128_bit_ids() {
|
|
CallLinkOwnership ownership;
|
|
CallLinkOwnership::LinkId a{};
|
|
CallLinkOwnership::LinkId b{};
|
|
a.fill(0x11);
|
|
b.fill(0xee);
|
|
|
|
std::array<CallLinkOwnership::LinkId, 4> mixed{};
|
|
for (size_t i = 0; i < a.size(); ++i) {
|
|
mixed[0][i] = i < 8 ? a[i] : b[i];
|
|
mixed[1][i] = (i & 1u) == 0 ? a[i] : b[i];
|
|
mixed[2][i] = i < 4 || (i >= 8 && i < 12) ? a[i] : b[i];
|
|
mixed[3][i] = i < 12 ? b[i] : a[i];
|
|
}
|
|
EXPECT_TRUE(ownership.publish(1, a));
|
|
|
|
std::atomic<bool> publishing{false};
|
|
std::atomic<bool> done{false};
|
|
std::atomic<bool> acceptedMixed{false};
|
|
std::atomic<uint64_t> completedChecks{0};
|
|
std::atomic<uint64_t> concurrentChecks{0};
|
|
std::thread reader([&] {
|
|
while (!done.load(std::memory_order_acquire)) {
|
|
for (const auto& candidate : mixed) {
|
|
if (ownership.generationFor(candidate) != 0) {
|
|
acceptedMixed.store(true, std::memory_order_release);
|
|
return;
|
|
}
|
|
completedChecks.fetch_add(1, std::memory_order_relaxed);
|
|
if (publishing.load(std::memory_order_acquire)) {
|
|
concurrentChecks.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
publishing.store(true, std::memory_order_release);
|
|
while (concurrentChecks.load(std::memory_order_acquire) == 0) {
|
|
std::this_thread::yield();
|
|
}
|
|
for (uint32_t generation = 2; generation < 100002; ++generation) {
|
|
EXPECT_TRUE(ownership.publish(
|
|
generation, (generation & 1u) == 0 ? b : a));
|
|
}
|
|
publishing.store(false, std::memory_order_release);
|
|
done.store(true, std::memory_order_release);
|
|
reader.join();
|
|
EXPECT_TRUE(completedChecks.load(std::memory_order_acquire) > 0);
|
|
EXPECT_TRUE(concurrentChecks.load(std::memory_order_acquire) > 0);
|
|
EXPECT_TRUE(!acceptedMixed.load(std::memory_order_acquire));
|
|
}
|
|
|
|
int main() {
|
|
RUN(replacement_rejects_all_stale_id_generation_pairs);
|
|
RUN(distinct_non_null_synthetic_links_require_exact_full_id);
|
|
RUN(stale_close_cannot_set_or_overwrite_new_owner_slot);
|
|
RUN(current_close_survives_stale_attempts_before_and_after);
|
|
RUN(stale_clear_cannot_detach_or_clear_new_owner_close);
|
|
RUN(concurrent_stale_and_current_close_publication_is_exact);
|
|
RUN(invalid_generations_are_never_published);
|
|
RUN(concurrent_publication_never_accepts_mixed_128_bit_ids);
|
|
std::printf("%d passed, %d failed\n", g_pass, g_fail);
|
|
return g_fail == 0 ? 0 : 1;
|
|
}
|