Affix
view release on metacpan or search on metacpan
t/999_marshaller_2.t view on Meta::CPAN
use v5.40;
use blib;
use Affix qw[:all];
use Test2::Tools::Affix qw[:all];
use Test2::V0 -no_srand => 1;
$|++;
#
my $C_CODE = <<'END_C';
#include "std.h"
#include <stdlib.h>
#include <stdarg.h>
//ext: .c
DLLEXPORT bool test_ptr(void*ptr) { warn("# c: %p", ptr); return true; }
typedef struct {
int x;
double y;
} Pos;
// Test passing struct by value
DLLEXPORT bool check_pos_val(Pos p) {
warn("# c: val {x: %d, y: %f}", p.x, p.y);
return (p.x == 10 && p.y == 20.5);
}
// Test passing struct by pointer
DLLEXPORT bool check_pos_ptr(Pos *p) {
warn("# c: ptr %p {x: %d, y: %f}", p, p->x, p->y);
return (p->x == 10 && p->y == 20.5);
}
typedef struct {
int id;
char name[16];
} Task;
typedef struct {
char name[32];
Task tasks[2]; // Array inside struct
} Employee;
typedef struct {
Employee *manager; // Pointer inside struct
int budget;
} Company;
// Verification function
DLLEXPORT bool verify_hierarchy(Company *c) {
if (!c || !c->manager) return false;
warn("# c: Company { budget: %d, manager: '%s' }", c->budget, c->manager->name);
warn("# c: Manager Task 0: [%d] %s", c->manager->tasks[0].id, c->manager->tasks[0].name);
return (c->budget == 50000 &&
strcmp(c->manager->name, "Alice") == 0 &&
c->manager->tasks[0].id == 101);
}
// Helper to return a NULL manager company
DLLEXPORT Company* get_null_company() {
static Company c = { .manager = NULL, .budget = 100 };
return &c;
}
// C++ Mock: Destructor Tracking
static int destructor_count = 0;
typedef struct { int id; } MockObj;
DLLEXPORT MockObj* mock_new(int id) {
MockObj* m = (MockObj*)malloc(sizeof(MockObj));
m->id = id;
return m;
}
DLLEXPORT void mock_delete(MockObj* m) {
destructor_count++;
free(m);
}
DLLEXPORT int get_destructor_count() { return destructor_count; }
// Function Pointer Pattern
typedef int (*calc_t)(int, int);
typedef struct {
calc_t operation;
} Calculator;
DLLEXPORT int run_calc(Calculator *c, int a, int b) {
if (!c || !c->operation) return -1;
return c->operation(a, b);
}
DLLEXPORT int debug_variadic(int count, ...) {
va_list args;
va_start(args, count);
warn("# c: debug_variadic received count=%d", count);
for (int i = 0; i < count; i++) {
int val = va_arg(args, int);
warn("# c: argument %d = %d", i, val);
}
va_end(args);
return count * 10; // Return something specific to check return logic
}
END_C
#
my $lib_path = compile_ok($C_CODE);
ok( $lib_path && -e $lib_path, 'Compiled a test shared library successfully' );
affix_ok $lib_path, 'test_ptr', [ Pointer [Void] ], Bool;
#
#~ ok my $ptr = malloc(1024), '$ptr = malloc(1024)';
ok my $ptr = alloc_owned(1024), '$ptr = alloc_owned(1024)';
ok is_pin($ptr), 'is_pin($ptr)';
#~ ok test_ptr($ptr), 'test_ptr($ptr)';
diag sprintf( "0x%016X", address($ptr) );
#
subtest struct => sub {
typedef Pos => Struct [ x => Int, y => Double ];
affix $lib_path, 'check_pos_val', [ Pos() ], Bool;
affix $lib_path, 'check_pos_ptr', [ Pointer [ Pos() ] ], Bool;
# Allocate raw memory
ok my $mem = alloc_owned( sizeof( Pos() ) ), 'alloc_owned memory for Pos';
# Cast the raw memory to our Pos struct
# This creates a magical 2.0 pin variable
ok my $p = cast( $mem, Pos() ), 'cast memory to Pos struct';
# Manipulate fields natively via magic
$p->{x} = 10;
$p->{y} = 20.5;
# Verify native fields
is $p->{x}, 10, 'field x is 10';
is $p->{y}, 20.5, 'field y is 20.5';
# Pass the magical pin to FFI (By Value)
# Affix will use memcpy because it's a pin
ok check_pos_val($p), 'FFI check_pos_val($p) - passed by value';
# Pass the magical pin to FFI (By Pointer)
# Affix will use get_address_v2 to resolve the pointer
ok check_pos_ptr($p), 'FFI check_pos_ptr($p) - passed by pointer';
diag sprintf( "Address: 0x%X", address($p) );
};
subtest company => sub {
typedef Task => Struct [ id => Int, name => Array [ Char, 16 ] ];
typedef Employee => Struct [ name => Array [ Char, 32 ], tasks => Array [ Task(), 2 ] ];
typedef Company => Struct [ manager => Pointer [ Employee() ], budget => Int ];
#
affix $lib_path, 'verify_hierarchy', [ Pointer [ Company() ] ], Bool;
# Setup nested data in C memory
ok my $mem_comp = alloc_owned( sizeof( Company() ) ), 'alloc Company';
ok my $mem_mgr = alloc_owned( sizeof( Employee() ) ), 'alloc Manager';
ok my $comp = cast( $mem_comp, Company() ), 'cast Company';
ok my $mgr = cast( $mem_mgr, Employee() ), 'cast Manager';
# Link them via pointer
$comp->{manager} = address($mgr);
$comp->{budget} = 50000;
is $comp->{budget}, 50000, 'Direct hash access to C memory works';
# Set deep values
$mgr->{name} = "Alice";
$mgr->{tasks}[0]{id} = 101;
$mgr->{tasks}[0]{name} = "FFI Core";
$mgr->{tasks}[1]{id} = 102;
$mgr->{tasks}[1]{name} = "Marshal v2";
# TEST DEEP ACCESS: $comp -> manager (ptr) -> tasks (array) -> name (string)
is $comp->{manager}{name}, "Alice", 'Deep Read: manager->name';
is $comp->{manager}{tasks}[0]{id}, 101, 'Deep Read: manager->tasks[0]->id';
# TEST DEEP WRITE via the pointer member
$comp->{manager}{tasks}[0]{id} = 999;
is $mgr->{tasks}[0]{id}, 999, 'Deep Write confirmed in original memory';
# Reset for verification function
$comp->{manager}{tasks}[0]{id} = 101;
# Pass to C
ok verify_hierarchy($comp), 'FFI: verify_hierarchy($comp) - deep validation passed';
};
subtest 'smart & safety' => sub {
# Types were defined in previous subtests
affix $lib_path, 'get_null_company', [], Pointer [ Company() ];
ok my $mem_comp = alloc_owned( sizeof( Company() ) ), 'alloc Company';
ok my $mem_mgr = alloc_owned( sizeof( Employee() ) ), 'alloc Manager';
ok my $comp = cast( $mem_comp, Company() ), 'cast Company';
ok my $mgr = cast( $mem_mgr, Employee() ), 'cast Manager';
$mgr->{name} = "Bob";
$comp->{budget} = 50000;
# We assign the $mgr pin DIRECTLY to the manager pointer field.
$comp->{manager} = $mgr;
is address( $comp->{manager} ), address($mgr), 'Smart Assignment: Pin converted to address automatically';
is $comp->{manager}{name}, "Bob", 'Data accessible through smart-assigned pointer';
# Traversing a NULL pointer in a struct
$comp->{manager} = undef; # Set C pointer to NULL
like dies { $comp->{manager}{name} }, qr[undefined value], 'Accessing NULL pointer member is a fatal exception';
# returning NULL
ok my $null_comp = get_null_company(), 'C returns pointer to struct with NULL member';
is $null_comp->{manager}, undef, 'C NULL pointer correctly becomes Perl undef';
# Deep Null
like dies { $null_comp->{manager}{tasks}[0]{id} }, qr[undefined value], 'Deep access on C NULL throws Perl exception';
};
subtest 'Giant Array & Anon Types' => sub {
my $type = Struct [ a => Int, b => Int ];
my $mem = alloc_owned( sizeof($type) );
# TEST ANONYMOUS TYPE EVAPORATION
{
my $p = cast( $mem, $type );
is $p->{a}, 0, 'Anonymous struct works';
}
# At this point, free_v2_pin was called, and the local arena for that
# struct definition is gone. No leak in the global registry!
# TEST GIANT ARRAY SWITCH
# Imagine a C array of 10,000 ints
typedef BigArray => Array [ Int, 10000 ];
$mem = alloc_owned( sizeof( BigArray() ) );
my $arr_pin = cast( $mem, BigArray() );
# $arr_pin is currently a scalar (Lazy Placeholder)
#~ ok !SvROK($arr_pin), 'Giant array is still a lazy scalar';
# Accessing it vivifies the AV
is $arr_pin->[500], 0, 'Accessing giant array element vivifies it on demand';
#~ ok SvROK($arr_pin), 'Now it is a real array reference';
};
subtest calculator => sub {
typedef MockObj => Struct [ id => Int ];
typedef calc_t => Callback [ [ Int, Int ] => Int ];
typedef Calculator => Struct [ operation => calc_t() ];
my $lib = Affix::load_library($lib_path); # Load library object for find_symbol
subtest 'calculator' => sub {
subtest 'C++ Destructors' => sub {
( run in 0.779 second using v1.01-cache-2.11-cpan-9789f410c06 )