Sponsoring The Perl Toolchain Summit 2025: Help make this important event another success Learn more

#pragma once
#include <catch2/catch_test_macros.hpp>
#include <xs.h>
using namespace xs;
enum class behaviour_t { VALID, EMPTY, THROWS };
struct perlvars {
SV* undef;
SV* iv;
SV* pv;
AV* av;
HV* hv;
SV* rv;
CV* cv;
SV* cvr;
GV* gv;
SV* gvr;
HV* stash;
SV* ov;
SV* ovr;
AV* oav;
SV* oavr;
HV* ohv;
SV* ohvr;
IO* io;
SV* ior;
GV* iog;
SV* iogr;
perlvars () {
undef = newSV(0);
iv = newSViv(1000);
pv = newSVpvs("hello");
av = newAV();
hv = newHV();
rv = newRV_noinc(newSViv(333));
cv = get_cv("M1::dummy", 0);
if (!cv) throw std::logic_error("should not happen");
cvr = newRV((SV*)cv);
stash = gv_stashpvs("M1", 0);
if (!stash) throw std::logic_error("should not happen");
ov = newSViv(123);
ovr = sv_bless(newRV_noinc(ov), stash);
oav = newAV();
oavr = sv_bless(newRV_noinc((SV*)oav), stash);
ohv = newHV();
ohvr = sv_bless(newRV_noinc((SV*)ohv), stash);
SV** gvref = hv_fetchs(stash, "class_method", 0);
if (!gvref) throw std::logic_error("should not happen");
gv = (GV*)(*gvref);
gvr = newRV((SV*)gv);
ior = SvREFCNT_inc(eval_pv("*STDOUT{IO}", 1));
io = (IO*)SvRV(ior);
iog = (GV*)eval_pv("*STDOUT", 1);
iogr = newRV((SV*)iog);
}
~perlvars () {
SvREFCNT_dec(undef);
SvREFCNT_dec(iv);
SvREFCNT_dec(pv);
SvREFCNT_dec(av);
SvREFCNT_dec(hv);
SvREFCNT_dec(rv);
SvREFCNT_dec(cvr);
SvREFCNT_dec(gvr);
SvREFCNT_dec(ovr);
SvREFCNT_dec(oavr);
SvREFCNT_dec(ohvr);
SvREFCNT_dec(ior);
SvREFCNT_dec(iogr);
}
};
template <class TestClass>
struct TestSv {
template <class T, class TC = T>
static void ctor (T* sv, behaviour_t behaviour, TC* check = nullptr) {
SECTION("default") { _ctor(sv, behaviour, Sv::INCREMENT, check); }
SECTION("noinc") { _ctor(sv, behaviour, Sv::NONE, check); }
}
template <class T>
static void ctor (T&& src, behaviour_t behaviour) {
SECTION("copy") { _ctor(src, behaviour, false); }
SECTION("move") { _ctor(std::move(src), behaviour, true); }
}
template <class T>
static void noinc (T* sv, behaviour_t behaviour) {
SvREFCNT_inc(sv);
auto rcnt = SvREFCNT(sv);
{
switch(behaviour) {
case behaviour_t::VALID:
{
TestClass o = TestClass::noinc(sv);
REQUIRE(SvREFCNT(sv) == rcnt);
REQUIRE(o);
}
break;
case behaviour_t::EMPTY:
{
TestClass o = TestClass::noinc(sv);
REQUIRE(!o);
REQUIRE(SvREFCNT(sv) == rcnt - 1);
}
break;
case behaviour_t::THROWS:
REQUIRE_THROWS_AS(TestClass::noinc(sv), std::invalid_argument);
break;
}
}
REQUIRE(SvREFCNT(sv) == rcnt-1);
}
template <class T, class TC = T>
static void assign (const TestClass& o, T* sv, behaviour_t behaviour, TC* check = nullptr) {
REQUIRE(o);
SECTION("default") { _assign(o, sv, behaviour, check); }
SECTION("to empty") { _assign(TestClass(), sv, behaviour, check); }
}
template <class T>
static void assign (const TestClass& o, T&& oth, behaviour_t behaviour) {
REQUIRE(o);
SECTION("copy") {
SECTION("default") { _assign(o, oth, behaviour, false); }
SECTION("to empty") { _assign(TestClass(), oth, behaviour, false); }
}
SECTION("move") {
SECTION("default") { _assign(o, std::move(oth), behaviour, true); }
SECTION("to empty") { _assign(TestClass(), std::move(oth), behaviour, true); }
}
}
private:
template <class T, class TC>
static void _ctor (T* sv, behaviour_t behaviour, bool policy, TC* check) {
if (!check) check = (TC*)sv;
auto cnt = SvREFCNT(check);
if (policy == Sv::NONE) SvREFCNT_inc(sv);
{
switch(behaviour) {
case behaviour_t::VALID:
{
TestClass o(sv, policy);
REQUIRE(o);
REQUIRE(SvREFCNT(check) == cnt+1);
REQUIRE(o.template get<TC>() == check);
}
break;
case behaviour_t::EMPTY:
{
TestClass o(sv, policy);
REQUIRE(!o);
REQUIRE(SvREFCNT(check) == cnt);
}
break;
case behaviour_t::THROWS:
REQUIRE_THROWS_AS(TestClass(sv, policy), std::invalid_argument);
REQUIRE(SvREFCNT(check) == cnt);
break;
}
}
REQUIRE(SvREFCNT(check) == cnt);
}
template <class T>
static void _ctor (T&& src, behaviour_t behaviour, bool move) {
SV* sv = src;
auto cnt = SvREFCNT(sv);
{
switch(behaviour) {
case behaviour_t::VALID:
{
TestClass o(std::forward<T>(src));
REQUIRE(o);
}
break;
case behaviour_t::EMPTY:
{
TestClass o(std::forward<T>(src));
REQUIRE(!o);
}
break;
case behaviour_t::THROWS:
REQUIRE_THROWS_AS(TestClass(std::forward<T>(src)), std::invalid_argument);
break;
}
if (move) REQUIRE(!src);
else REQUIRE(src);
}
REQUIRE(SvREFCNT(sv) == cnt-move);
}
template <class T, class TC>
static void _assign (const TestClass& co, T* sv, behaviour_t behaviour, TC* check) {
if (!check) check = (TC*)sv;
SV* src = co;
auto src_cnt = src ? SvREFCNT(src) : 0;
auto cnt = SvREFCNT(check);
{
TestClass o(co);
switch(behaviour) {
case behaviour_t::VALID:
o = sv;
REQUIRE(o);
REQUIRE(o.template get<TC>() == check);
REQUIRE(SvREFCNT(check) == cnt + 1);
break;
case behaviour_t::EMPTY:
o = sv;
REQUIRE(!o);
REQUIRE(o.template get<TC>() == nullptr);
REQUIRE(SvREFCNT(check) == cnt);
break;
case behaviour_t::THROWS:
REQUIRE_THROWS_AS(o = sv, std::invalid_argument);
REQUIRE(SvREFCNT(check) == cnt);
break;
}
if (src) REQUIRE(SvREFCNT(src) == src_cnt);
}
REQUIRE(SvREFCNT(check) == cnt);
if (src) REQUIRE(SvREFCNT(src) == src_cnt);
}
template <class T>
static void _assign (const TestClass& co, T&& oth, behaviour_t behaviour, bool move) {
SV* src = co;
SV* sv = oth;
auto src_cnt = src ? SvREFCNT(src) : 0;
auto cnt = SvREFCNT(sv);
{
TestClass o(co);
switch(behaviour) {
case behaviour_t::VALID:
o = std::forward<T>(oth);
REQUIRE(o);
break;
case behaviour_t::EMPTY:
o = std::forward<T>(oth);
REQUIRE(!o);
break;
case behaviour_t::THROWS:
REQUIRE_THROWS_AS(o = std::forward<T>(oth), std::invalid_argument);
break;
}
if (move) REQUIRE(oth.get() == src);
else REQUIRE(oth.get() == sv);
REQUIRE(SvREFCNT(sv) == cnt+(behaviour == behaviour_t::VALID)-move);
if (src) REQUIRE(SvREFCNT(src) == src_cnt+move);
}
REQUIRE(SvREFCNT(sv) == cnt-move);
if (src) REQUIRE(SvREFCNT(src) == src_cnt+move);
}
};