xaverius: expand registry tests for tombstone and DIX publication

Add real-header registry tests covering tombstone transitions, module-role finalize/shutdown ownership, and DIX-backed PhysicalDevice publication/duplicate-publish behavior using actual registry and resource APIs.
This commit is contained in:
Enrico Weigelt, metux IT consult 2026-11-11 16:04:23 +01:00
commit dbe1efc284

View file

@ -12,6 +12,11 @@
#include <string.h>
#include <stdlib.h>
#include "dix/dix_priv.h"
#include "dix/resource_priv.h"
#include "include/dixstruct.h"
#include "include/resource.h"
#include "os/client_priv.h"
#include "tests-common.h"
#include "xaverius_priv.h"
@ -90,7 +95,6 @@ xaverius_module_role_descriptor_validation(void)
static void
xaverius_identity_comparison(void)
{
/* PCI equality */
{
XaveriusDeviceIdentity a = { 0 };
XaveriusDeviceIdentity b = { 0 };
@ -101,7 +105,6 @@ xaverius_identity_comparison(void)
assert(!xaverius_identity_equal(&a, &b));
}
/* DRM equality */
{
XaveriusDeviceIdentity a = { 0 };
XaveriusDeviceIdentity b = { 0 };
@ -173,7 +176,6 @@ xaverius_ordering_key(void)
assert(strncmp(buf, "pci:0000:01:00.0::", 18) == 0);
assert(strstr(buf, "test-role") != NULL);
/* DRM identity produces different canonical key (ordering uses canonical identity) */
{
XaveriusDeviceIdentity drm = { 0 };
drm.drm_major = 226;
@ -185,13 +187,11 @@ xaverius_ordering_key(void)
assert(strcmp(buf, dbuf) != 0);
}
/* Too-small buffer must fail (no silent truncation) */
{
char tiny[8];
assert(xaverius_create_ordering_key(tiny, sizeof(tiny), &pci, "long-owner-key") == XAVERIUS_REGISTRY_INVALID_ARGUMENT);
}
/* Bytewise ordering comparator */
{
char a[128], b[128];
assert(xaverius_create_ordering_key(a, sizeof(a), &pci, "aaa") == XAVERIUS_REGISTRY_SUCCESS);
@ -208,7 +208,20 @@ xaverius_module_role_ownership(void)
XaveriusModuleRoleDescriptor desc;
XaveriusModuleRole *role = NULL;
/* Successful create does not call hooks */
/*
* Ownership plan (module-owner original ref + binding refs):
* - XaveriusModuleRoleCreate() creates and registers a role; no hook called.
* The module owner holds the original role reference.
* - XaveriusModuleRoleRetain/Release() are lifetime hook primitives.
* - xaverius_binding_alloc() retains the role for a binding; xaverius_binding_free()
* releases that binding reference (exactly once per binding lifetime).
* - XaveriusModuleRoleFinalize() invokes the release hook once to drop the
* module owner's original reference and frees server-owned role storage.
* Caller must invoke finalize exactly once after all binding refs are gone.
* - XaveriusRegistryFini() finalizes any remaining roles via Finalize() to
* perform ordered shutdown cleanup.
*/
make_valid_desc(&desc, "owner-a");
reset_hook_counts();
assert(XaveriusModuleRoleCreate(&desc, &role) == XAVERIUS_REGISTRY_SUCCESS);
@ -216,17 +229,16 @@ xaverius_module_role_ownership(void)
assert(test_retain_calls == 0);
assert(test_release_calls == 0);
/* Explicit retain/release balance */
XaveriusModuleRoleRetain(role);
assert(test_retain_calls == 1);
XaveriusModuleRoleRelease(role);
assert(test_release_calls == 1);
/* Finalize performs final release path */
/* Finalize releases original owner reference and frees role storage. */
XaveriusModuleRoleFinalize(role);
assert(test_release_calls == 2);
/* Invalid descriptor: no hooks called */
/* Invalid descriptors must not invoke hooks. */
make_valid_desc(&desc, "owner-b");
desc.abi_version = XAVERIUS_MODULE_ROLE_DESCRIPTOR_ABI_VERSION + 1;
reset_hook_counts();
@ -236,7 +248,6 @@ xaverius_module_role_ownership(void)
assert(test_retain_calls == 0);
assert(test_release_calls == 0);
/* Missing hook: rejected, no hooks called */
make_valid_desc(&desc, "owner-c");
desc.retain_module_role = NULL;
reset_hook_counts();
@ -257,7 +268,6 @@ test_query_snapshot_ok(struct _XaveriusDeviceBackendBinding *binding,
snapshot_calls++;
if (snapshot_result_mode != XAVERIUS_BACKEND_SUCCESS)
return snapshot_result_mode;
/* Registry deep-copies and frees the callback-provided snapshot buffer. */
out->size = 16;
out->data = calloc(1, out->size);
assert(out->data != NULL);
@ -312,7 +322,6 @@ xaverius_registry_registration_paths(void)
assert(XaveriusRegistryInit());
/* Pre-retain/pre-binding validation failures must not call role hooks */
make_valid_desc(&desc, "reg-owner");
reset_hook_counts();
assert(XaveriusModuleRoleCreate(&desc, &role) == XAVERIUS_REGISTRY_SUCCESS);
@ -320,27 +329,25 @@ xaverius_registry_registration_paths(void)
id.bus = 1;
memset(id.driver_uuid, 0x44, sizeof(id.driver_uuid));
/* Missing snapshot callback -> INVALID_ARGUMENT, no retain/release */
/* Pre-binding validation failures must not call role hooks. */
setup_min_ops(&ops, true);
ops.query_physical_snapshot = NULL;
setup_min_reg(&reg, &id, &ops, role);
assert(XaveriusRegistryRegisterDdxDevice(NULL, &reg, &dev, &dev_gen, &bind_gen) == XAVERIUS_REGISTRY_INVALID_ARGUMENT);
assert(test_retain_calls == 0 && test_release_calls == 0);
/* Not-registered role generation -> NOT_REGISTERED, no hook calls */
setup_min_ops(&ops, true);
setup_min_reg(&reg, &id, &ops, role);
reg.owner_role_generation = role->role_generation + 9999;
assert(XaveriusRegistryRegisterDdxDevice(NULL, &reg, &dev, &dev_gen, &bind_gen) == XAVERIUS_REGISTRY_NOT_REGISTERED);
assert(test_retain_calls == 0 && test_release_calls == 0);
/* Invalid registration ABI -> INVALID_ARGUMENT, no hook calls */
setup_min_reg(&reg, &id, &ops, role);
reg.abi_version = XAVERIUS_DEVICE_BACKEND_REGISTRATION_ABI_VERSION + 1;
assert(XaveriusRegistryRegisterDdxDevice(NULL, &reg, &dev, &dev_gen, &bind_gen) == XAVERIUS_REGISTRY_INVALID_ARGUMENT);
assert(test_retain_calls == 0 && test_release_calls == 0);
/* Snapshot callback failure after binding alloc: binding retain balanced by cleanup */
/* Snapshot callback failure after binding alloc: retain/release balanced. */
reg.abi_version = XAVERIUS_DEVICE_BACKEND_REGISTRATION_ABI_VERSION;
snapshot_calls = 0;
snapshot_result_mode = XAVERIUS_BACKEND_UNAVAILABLE;
@ -351,7 +358,7 @@ xaverius_registry_registration_paths(void)
assert(test_retain_calls == 1);
assert(test_release_calls == 1);
/* Invalid snapshot content: balanced cleanup */
/* Invalid snapshot content: retain/release balanced. */
snapshot_result_mode = XAVERIUS_BACKEND_SUCCESS;
ops.query_physical_snapshot = test_query_snapshot_bad;
reg.ops = &ops;
@ -362,7 +369,7 @@ xaverius_registry_registration_paths(void)
assert(test_retain_calls == 1);
assert(test_release_calls == 1);
/* Successful registration */
/* Successful registration: binding retains role once. */
setup_min_ops(&ops, true);
setup_min_reg(&reg, &id, &ops, role);
snapshot_calls = 0;
@ -374,9 +381,9 @@ xaverius_registry_registration_paths(void)
assert(test_retain_calls == 1);
assert(test_release_calls == 0);
assert(dev->binding != NULL);
assert(dev->xid == 0); /* publication is separate */
assert(dev->xid == 0);
/* Exact replay: additional caller ref, no new binding/retain */
/* Exact replay: additional caller ref, no new binding/retain. */
{
XaveriusPhysicalDevice *dev2 = NULL;
uint64_t dg2 = 0, bg2 = 0;
@ -392,7 +399,7 @@ xaverius_registry_registration_paths(void)
assert(dev->caller_refcount == ref_before);
}
/* Conflict: same identity, different owning role */
/* Conflict: same identity, different owning role -> no hook calls. */
{
XaveriusModuleRoleDescriptor desc2;
XaveriusModuleRole *role2 = NULL;
@ -401,31 +408,164 @@ xaverius_registry_registration_paths(void)
XaveriusPhysicalDevice *devc = NULL;
uint64_t dgc = 0, bgc = 0;
make_valid_desc(&desc2, "reg-owner-2");
reset_hook_counts();
assert(XaveriusModuleRoleCreate(&desc2, &role2) == XAVERIUS_REGISTRY_SUCCESS);
assert(test_retain_calls == 0 && test_release_calls == 0);
setup_min_ops(&ops2, true);
setup_min_reg(&reg2, &id, &ops2, role2);
reset_hook_counts();
assert(XaveriusRegistryRegisterDdxDevice(NULL, &reg2, &devc, &dgc, &bgc) == XAVERIUS_REGISTRY_IDENTITY_CONFLICT);
assert(test_retain_calls == 0);
assert(test_release_calls == 0);
/* Binding removal releases exactly one retained binding role ref. */
reset_hook_counts();
assert(XaveriusRegistryRemoveDeviceBackendBinding(NULL, dev, dev_gen, role->role_generation, bind_gen) == XAVERIUS_REGISTRY_SUCCESS);
assert(dev->binding == NULL);
assert(test_release_calls == 1);
XaveriusPhysicalDeviceRelease(dev);
/* Shutdown finalizes remaining roles (owner original release once each). */
reset_hook_counts();
XaveriusRegistryFini();
assert(test_release_calls == 2); /* role + role2 */
return;
}
/* Binding removal releases exactly one retained binding role ref */
reset_hook_counts();
assert(XaveriusRegistryRemoveDeviceBackendBinding(NULL, dev, dev_gen, role->role_generation, bind_gen) == XAVERIUS_REGISTRY_SUCCESS);
assert(dev->binding == NULL);
assert(test_release_calls == 1);
/* Caller ref release is separate and does not free registry record */
XaveriusPhysicalDeviceRelease(dev);
/* Registry shutdown cleans remaining records/resources.
* (Exercise cleanup on a registry state without a registered DIX
* PhysicalDevice resource type in this isolated unit-test process.) */
XaveriusPhysicalDeviceType = 0;
/* Fallback (not reached) */
XaveriusRegistryFini();
}
static void
xaverius_registry_tombstone_lifecycle(void)
{
XaveriusModuleRoleDescriptor desc;
XaveriusModuleRole *role = NULL;
XaveriusDeviceIdentity id = { 0 };
struct XaveriusDeviceBackendOps ops;
XaveriusDeviceBackendRegistration reg;
XaveriusPhysicalDevice *dev = NULL;
uint64_t dev_gen = 0, bind_gen = 0;
assert(XaveriusRegistryInit());
make_valid_desc(&desc, "tomb-owner");
reset_hook_counts();
assert(XaveriusModuleRoleCreate(&desc, &role) == XAVERIUS_REGISTRY_SUCCESS);
id.bus = 7;
id.function = 1;
memset(id.driver_uuid, 0x55, sizeof(id.driver_uuid));
setup_min_ops(&ops, true);
snapshot_result_mode = XAVERIUS_BACKEND_SUCCESS;
setup_min_reg(&reg, &id, &ops, role);
reset_hook_counts();
assert(XaveriusRegistryRegisterDdxDevice(NULL, &reg, &dev, &dev_gen, &bind_gen) == XAVERIUS_REGISTRY_SUCCESS);
assert(dev != NULL);
assert(test_retain_calls == 1 && test_release_calls == 0);
assert(xaverius_physical_device_count == 1);
assert(xaverius_tombstone_count == 0);
{
XaveriusPhysicalDevice *obs_dev = NULL;
uint64_t obs_gen = 0;
const char source[] = "src-tomb";
uint64_t obs_id = 1001;
assert(XaveriusRegistryObserveDevice(NULL, &id, sizeof(id),
source, sizeof(source),
1, obs_id,
&obs_dev, &obs_gen) == XAVERIUS_REGISTRY_SUCCESS);
assert(obs_dev == dev);
/* Confirm removal moves device exactly once to tombstone list. */
assert(XaveriusRegistryConfirmDeviceRemoved(NULL, dev, dev_gen, &id, sizeof(id)) == XAVERIUS_REGISTRY_SUCCESS);
assert(dev->status == XAVERIUS_REGISTRY_REMOVED);
assert(xaverius_physical_device_count == 0);
assert(xaverius_tombstone_count == 1);
assert(dev->xid == 0); /* unpublished device keeps no XID */
/* Second removal attempt must not move again. */
assert(XaveriusRegistryConfirmDeviceRemoved(NULL, dev, dev_gen, &id, sizeof(id)) == XAVERIUS_REGISTRY_STALE_EVENT);
assert(xaverius_physical_device_count == 0);
assert(xaverius_tombstone_count == 1);
}
/* Ordered shutdown frees tombstones and finalizes remaining role. */
reset_hook_counts();
XaveriusRegistryFini();
assert(test_release_calls == 1); /* binding-retained role ref finalized on shutdown cleanup path */
}
static void
xaverius_registry_publish_dix(void)
{
static ClientRec test_client;
static int dix_inited;
XaveriusModuleRoleDescriptor desc;
XaveriusModuleRole *role = NULL;
XaveriusDeviceIdentity id = { 0 };
struct XaveriusDeviceBackendOps ops;
XaveriusDeviceBackendRegistration reg;
XaveriusPhysicalDevice *dev = NULL;
uint64_t dev_gen = 0, bind_gen = 0;
void *res = NULL;
int rc;
if (!dix_inited) {
dixResetPrivates();
memset(&test_client, 0, sizeof(test_client));
test_client.index = 1;
test_client.clientAsMask = ((XID)1 << CLIENTOFFSET);
serverClient = &test_client;
InitClient(serverClient, 0, (void *)NULL);
assert(InitClientResources(serverClient));
dix_inited = 1;
}
XaveriusPhysicalDeviceType = 0;
assert(XaveriusRegistryInit());
make_valid_desc(&desc, "pub-owner");
assert(XaveriusModuleRoleCreate(&desc, &role) == XAVERIUS_REGISTRY_SUCCESS);
id.bus = 3;
id.device = 2;
memset(id.driver_uuid, 0x66, sizeof(id.driver_uuid));
setup_min_ops(&ops, true);
snapshot_result_mode = XAVERIUS_BACKEND_SUCCESS;
setup_min_reg(&reg, &id, &ops, role);
assert(XaveriusRegistryRegisterDdxDevice(NULL, &reg, &dev, &dev_gen, &bind_gen) == XAVERIUS_REGISTRY_SUCCESS);
assert(dev != NULL);
assert(dev->xid == 0);
/* Successful publication with real DIX resource registration. */
assert(XaveriusPhysicalDevicePublish(dev) == true);
assert(dev->xid != 0);
res = NULL;
rc = dixLookupResourceByType(&res, dev->xid, XaveriusPhysicalDeviceType,
serverClient, DixReadAccess);
assert(rc == Success);
assert(res == dev);
/* Duplicate publication must fail and keep same XID/resource mapping. */
{
XID old = dev->xid;
assert(XaveriusPhysicalDevicePublish(dev) == false);
assert(dev->xid == old);
res = NULL;
rc = dixLookupResourceByType(&res, dev->xid, XaveriusPhysicalDeviceType,
serverClient, DixReadAccess);
assert(rc == Success);
assert(res == dev);
}
/* Ordered shutdown must release DIX wrapper and registry/tombstone state. */
reset_hook_counts();
XaveriusRegistryFini();
assert(test_release_calls >= 1);
}
const testfunc_t*
xaverius_registry_test(void)
{
@ -435,6 +575,8 @@ xaverius_registry_test(void)
xaverius_ordering_key,
xaverius_module_role_ownership,
xaverius_registry_registration_paths,
xaverius_registry_tombstone_lifecycle,
xaverius_registry_publish_dix,
NULL,
};