519 lines
26 KiB
C++
519 lines
26 KiB
C++
// gfx_gen, part 4: the self-test of the generated wrappers.
|
|
//
|
|
// The wrappers are mechanical, so the test is mechanical too. gfx_mock.cpp defines every raylib / rlgl function the
|
|
// wrappers call (with raylib's own signatures, taken from the real headers, so a mismatch is a compile error): the
|
|
// mock records a fingerprint of each argument exactly as raylib would have received it, and returns a sentinel.
|
|
// gfx_selftest.cpp calls every wrapper with distinctive values (every parameter, every struct field, every matrix
|
|
// element gets its own number) and checks that raylib saw exactly those values, in order, and that the sentinel
|
|
// comes back intact. raymath functions are real inline code, so there the wrapper's result is compared with a
|
|
// direct call of the raymath function ("oracle").
|
|
// A swapped parameter, a wrong cast or a transposed matrix cannot pass.
|
|
#pragma once
|
|
|
|
#include "emit_api.hpp"
|
|
|
|
namespace gen {
|
|
|
|
class TestWriter {
|
|
public:
|
|
explicit TestWriter(const Plan& p) : p_(p), m_(p.m_), ty_(p.ty_) {}
|
|
|
|
static std::string common() {
|
|
return R"(// GENERATED by tools/gfx_gen. DO NOT EDIT.
|
|
#pragma once
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
namespace gfxtest {
|
|
|
|
struct Hash {
|
|
std::uint64_t v = 1469598103934665603ull;
|
|
void add(std::uint64_t x) {
|
|
for (int i = 0; i < 8; ++i) { v ^= (x >> (8 * i)) & 0xffu; v *= 1099511628211ull; }
|
|
}
|
|
};
|
|
|
|
inline void fp(Hash& h, bool x) { h.add(x ? 1u : 0u); }
|
|
template <class T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
|
|
inline void fp(Hash& h, T x) { h.add(static_cast<std::uint64_t>(static_cast<std::int64_t>(x))); }
|
|
template <class T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
|
|
inline void fp(Hash& h, T x) { h.add(static_cast<std::uint64_t>(static_cast<std::int64_t>(x))); }
|
|
inline void fp(Hash& h, float x) { std::uint32_t b; std::memcpy(&b, &x, 4); h.add(b); }
|
|
inline void fp(Hash& h, double x) { std::uint64_t b; std::memcpy(&b, &x, 8); h.add(b); }
|
|
template <class T> inline void fpPtr(Hash& h, const T* p) { h.add(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(p))); }
|
|
inline void fpStr(Hash& h, const char* s) {
|
|
if (!s) { h.add(0); return; }
|
|
h.add(1);
|
|
for (; *s; ++s) h.add(static_cast<unsigned char>(*s));
|
|
}
|
|
|
|
template <class T> inline std::uint64_t hv(const T& x) { Hash h; fp(h, x); return h.v; }
|
|
template <class T> inline std::uint64_t hvPtr(const T* p) { Hash h; fpPtr(h, p); return h.v; }
|
|
inline std::uint64_t hvStr(const char* s) { Hash h; fpStr(h, s); return h.v; }
|
|
template <class F> inline std::uint64_t hvFn(F f) { Hash h; h.add(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(f))); return h.v; }
|
|
|
|
// The compiler must not know the sentinel values. raymath's sinf/cosf/tan on a value it knows would be folded at
|
|
// compile time with the compiler's own rounding, which can differ in the last bit from the C library at run time;
|
|
// then a correct wrapper would be reported as "differs from calling raymath directly".
|
|
template <class T> inline T opaque(T v) {
|
|
static_assert(std::is_trivially_copyable_v<T>, "opaque() copies bytes");
|
|
unsigned char raw[sizeof(T)];
|
|
std::memcpy(raw, &v, sizeof(T));
|
|
const volatile unsigned char* p = raw;
|
|
unsigned char out[sizeof(T)];
|
|
for (std::size_t i = 0; i < sizeof(T); ++i) out[i] = p[i];
|
|
std::memcpy(&v, out, sizeof(T));
|
|
return v;
|
|
}
|
|
|
|
// Distinctive values. The same salt gives the same value in the engine's types and in raylib's.
|
|
template <class T> inline T makeScalar(int salt) {
|
|
if constexpr (std::is_same_v<T, bool>) return opaque<bool>((salt % 3) != 0);
|
|
else if constexpr (std::is_floating_point_v<T>) return opaque<T>(static_cast<T>(static_cast<double>(salt) * 0.37 + 0.5));
|
|
else if constexpr (std::is_enum_v<T>) return opaque<T>(static_cast<T>(0));
|
|
else if constexpr (sizeof(T) == 1) return opaque<T>(static_cast<T>(salt % 120 + 1));
|
|
else return opaque<T>(static_cast<T>(salt % 30011 + 1));
|
|
}
|
|
template <class P> inline P makePtr(int salt) { return reinterpret_cast<P>(static_cast<std::uintptr_t>(0x100000 + static_cast<std::uintptr_t>(salt) * 64)); }
|
|
inline const char* makeStr(int salt) {
|
|
static std::deque<std::string> store;
|
|
store.push_back("str" + std::to_string(salt));
|
|
return store.back().c_str();
|
|
}
|
|
|
|
} // namespace gfxtest
|
|
)";
|
|
}
|
|
|
|
static std::string iface() {
|
|
return R"(// GENERATED by tools/gfx_gen. DO NOT EDIT.
|
|
#pragma once
|
|
#include "gfx_selftest_common.hpp"
|
|
|
|
#include <cstdio>
|
|
#include <initializer_list>
|
|
|
|
namespace gfxtest {
|
|
|
|
struct Call {
|
|
const char* name = "";
|
|
std::vector<std::uint64_t> args;
|
|
std::uint64_t ret = 0;
|
|
};
|
|
|
|
// implemented by gfx_mock.cpp
|
|
void reset();
|
|
const std::vector<Call>& calls();
|
|
int oracleCount();
|
|
const char* oracleName(int i);
|
|
// Runs raymath function i directly on the sentinel arguments; hashes = arguments, result, out-parameters.
|
|
bool oracleRun(int i, std::vector<std::uint64_t>& hashes);
|
|
|
|
struct Ctx {
|
|
int checks = 0, failures = 0;
|
|
void fail(const char* what, const std::string& why) {
|
|
++failures;
|
|
std::printf("FAIL %s: %s\n", what, why.c_str());
|
|
}
|
|
void expectCall(const char* name, std::initializer_list<std::uint64_t> args, bool hasRet, std::uint64_t ret) {
|
|
++checks;
|
|
const std::vector<Call>& c = calls();
|
|
if (c.size() != 1) { fail(name, "expected exactly one raylib call, saw " + std::to_string(c.size())); return; }
|
|
if (std::strcmp(c[0].name, name) != 0) { fail(name, std::string("called ") + c[0].name + " instead"); return; }
|
|
if (c[0].args.size() != args.size()) { fail(name, "wrong number of arguments"); return; }
|
|
size_t i = 0;
|
|
for (std::uint64_t a : args) {
|
|
if (c[0].args[i] != a) { fail(name, "argument " + std::to_string(i + 1) + " reached raylib changed or in the wrong place"); return; }
|
|
++i;
|
|
}
|
|
if (hasRet && c[0].ret != ret) fail(name, "the value raylib returned came back changed");
|
|
}
|
|
void expectEqual(const char* name, const std::vector<std::uint64_t>& got, const std::vector<std::uint64_t>& want) {
|
|
++checks;
|
|
if (got.size() != want.size()) { fail(name, "result count differs from the direct call"); return; }
|
|
for (size_t i = 0; i < got.size(); ++i)
|
|
if (got[i] != want[i]) { fail(name, "value " + std::to_string(i + 1) + " differs from calling raymath directly"); return; }
|
|
}
|
|
};
|
|
|
|
} // namespace gfxtest
|
|
)";
|
|
}
|
|
|
|
// ---- gfx_mock.cpp ------------------------------------------------------------------------------------------
|
|
std::string mock(const std::string& version) const {
|
|
std::ostringstream o;
|
|
o << "// GENERATED by tools/gfx_gen from raylib " << version << ". DO NOT EDIT.\n"
|
|
<< "// A recording stand-in for raylib: see the header comment of gfx_gen's emit_test.hpp.\n"
|
|
<< "#include \"gfx_selftest_iface.hpp\"\n\n#include <cstdarg>\n#include <cstdint>\n\n#include <raylib.h>\n";
|
|
if (p_.enabled(kGroupRlgl)) o << "#include <rlgl.h>\n";
|
|
if (p_.enabled(kGroupRaymath)) o << "#include <raymath.h>\n";
|
|
o << "\nnamespace gfxtest {\nnamespace {\nstd::vector<Call> g_calls;\n}\n\n";
|
|
o << "void reset() { g_calls.clear(); }\nconst std::vector<Call>& calls() { return g_calls; }\n";
|
|
o << "inline Call& begin(const char* name) {\n g_calls.emplace_back();\n g_calls.back().name = name;\n return g_calls.back();\n}\n\n";
|
|
structFns(o, true);
|
|
o << "} // namespace gfxtest\n\n";
|
|
|
|
// the raylib / rlgl functions, with raylib's signatures
|
|
for (const Wrapped& w : p_.wrapped) {
|
|
if (w.f->group == kGroupRaymath) continue;
|
|
if (w.special) {
|
|
if (w.f->name == "TraceLog")
|
|
o << "void TraceLog(int a0, const char* a1, ...) {\n gfxtest::Call& c = gfxtest::begin(\"TraceLog\");\n"
|
|
<< " va_list args;\n va_start(args, a1);\n const char* msg = va_arg(args, const char*); // the wrapper always passes \"%s\", message\n"
|
|
<< " va_end(args);\n c.args = {gfxtest::hv(a0), gfxtest::hvStr(a1), gfxtest::hvStr(msg)};\n}\n\n";
|
|
continue;
|
|
}
|
|
mockFunction(o, w);
|
|
}
|
|
|
|
// raymath oracles
|
|
o << "namespace gfxtest {\n";
|
|
std::vector<const Wrapped*> ray = raymathList();
|
|
o << "int oracleCount() { return " << ray.size() << "; }\n";
|
|
o << "const char* oracleName(int i) {\n static const char* names[] = {";
|
|
for (const Wrapped* w : ray) o << "\"" << w->f->name << "\", ";
|
|
o << "\"\"};\n return names[i];\n}\n";
|
|
o << "bool oracleRun(int i, std::vector<std::uint64_t>& hashes) {\n hashes.clear();\n switch (i) {\n";
|
|
for (size_t k = 0; k < ray.size(); ++k) {
|
|
o << " case " << k << ": {\n";
|
|
if (!oracleTestable(*ray[k])) { o << " return false;\n }\n"; continue; }
|
|
oracleBody(o, *ray[k], k, true);
|
|
o << " return true;\n }\n";
|
|
}
|
|
o << " default: break;\n }\n return false;\n}\n} // namespace gfxtest\n";
|
|
return o.str();
|
|
}
|
|
|
|
// ---- gfx_selftest.cpp --------------------------------------------------------------------------------------
|
|
std::string test(const std::string& version) const {
|
|
std::ostringstream o;
|
|
o << "// GENERATED by tools/gfx_gen from raylib " << version << ". DO NOT EDIT.\n"
|
|
<< "#include <engine/gfx_api.hpp>\n";
|
|
if (p_.enabled(kGroupRlgl)) o << "#include <engine/gfx_rlgl_api.hpp>\n";
|
|
if (p_.enabled(kGroupRaymath)) o << "#include <engine/gfx_raymath_api.hpp>\n";
|
|
o << "#include \"gfx_selftest_iface.hpp\"\n\nnamespace gfx = eng::gfx;\nusing namespace eng::gfx;\nusing eng::Mat4;\nusing eng::Vec2;\nusing eng::Vec3;\nusing eng::Quat;\nusing eng::Rgba;\nusing eng::Aabb;\n\n";
|
|
o << "namespace gfxtest {\n";
|
|
structFns(o, false);
|
|
// callback stand-ins
|
|
for (const std::string& n : p_.emittedCallbacks) {
|
|
const CallbackDef& c = m_.callbacks[m_.callbackIdx.at(n)];
|
|
o << "[[maybe_unused]] static " << p_.gfxType(ty_.classify(c.ret), true) << " cb_" << n << "(";
|
|
for (size_t i = 0; i < c.params.size(); ++i) o << (i ? ", " : "") << p_.gfxType(ty_.classify(c.params[i].type), true);
|
|
o << ") { ";
|
|
const Res r = ty_.classify(c.ret);
|
|
if (r.cat != Cat::Void) o << "return " << (r.t.ptr ? "nullptr" : "{}") << "; ";
|
|
o << "}\n";
|
|
}
|
|
o << "} // namespace gfxtest\n\n";
|
|
|
|
size_t idx = 0;
|
|
std::vector<std::string> fnNames;
|
|
for (const Wrapped& w : p_.wrapped) {
|
|
if (w.f->group == kGroupRaymath || w.special) continue;
|
|
const std::string fn = "t" + std::to_string(idx++);
|
|
fnNames.push_back(fn);
|
|
testFunction(o, w, fn);
|
|
}
|
|
std::vector<const Wrapped*> ray = raymathList();
|
|
for (size_t k = 0; k < ray.size(); ++k) {
|
|
const std::string fn = "r" + std::to_string(k);
|
|
fnNames.push_back(fn);
|
|
o << "static void " << fn << "(gfxtest::Ctx& c) { // " << ray[k]->f->name << " -> gfx::" << ray[k]->name << "\n";
|
|
if (!oracleTestable(*ray[k])) { o << " (void)c;\n}\n\n"; continue; }
|
|
o << " std::vector<std::uint64_t> got, want;\n";
|
|
o << " if (!gfxtest::oracleRun(" << k << ", want)) return;\n";
|
|
o << " if (std::string(gfxtest::oracleName(" << k << ")) != \"" << ray[k]->f->name << "\") { c.fail(\"" << ray[k]->f->name << "\", \"oracle table out of step\"); return; }\n";
|
|
oracleBody(o, *ray[k], k, false);
|
|
o << " c.expectEqual(\"" << ray[k]->f->name << "\", got, want);\n}\n\n";
|
|
}
|
|
|
|
o << "int main() {\n gfxtest::Ctx c;\n";
|
|
for (const std::string& f : fnNames) o << " " << f << "(c);\n";
|
|
o << R"( // printf-style functions
|
|
{
|
|
const std::string s = gfx::textFormat("%d-%s-%.2f", 7, "q", 1.5);
|
|
++c.checks;
|
|
if (s != "7-q-1.50") c.fail("textFormat", "got '" + s + "'");
|
|
std::string big(3000, 'x');
|
|
const std::string s2 = gfx::textFormat("<%s>", big.c_str());
|
|
++c.checks;
|
|
if (s2 != "<" + big + ">") c.fail("textFormat", "long strings must not be cut off");
|
|
}
|
|
)";
|
|
if (hasSpecial("TraceLog"))
|
|
o << R"( {
|
|
gfxtest::reset();
|
|
gfx::traceLog(3, "x=%d %s", 5, "abc");
|
|
c.expectCall("TraceLog", {gfxtest::hv(3), gfxtest::hvStr("%s"), gfxtest::hvStr("x=5 abc")}, false, 0);
|
|
}
|
|
)";
|
|
o << " std::printf(\"gfx_selftest: %d checks, %d failed\\n\", c.checks, c.failures);\n return c.failures == 0 ? 0 : 1;\n}\n";
|
|
return o.str();
|
|
}
|
|
|
|
size_t raymathCount() const { return raymathList().size(); }
|
|
size_t mockedCount() const {
|
|
size_t n = 0;
|
|
for (const Wrapped& w : p_.wrapped) if (w.f->group != kGroupRaymath && !w.special) ++n;
|
|
return n;
|
|
}
|
|
size_t oracleTestableCount() const {
|
|
size_t n = 0;
|
|
for (const Wrapped* w : raymathList()) if (oracleTestable(*w)) ++n;
|
|
return n;
|
|
}
|
|
|
|
private:
|
|
const Plan& p_;
|
|
const ModelData& m_;
|
|
const Types& ty_;
|
|
|
|
bool hasSpecial(const std::string& rlName) const {
|
|
for (const Wrapped& w : p_.wrapped) if (w.special && w.f->name == rlName) return true;
|
|
return false;
|
|
}
|
|
|
|
std::vector<const Wrapped*> raymathList() const {
|
|
std::vector<const Wrapped*> v;
|
|
for (const Wrapped& w : p_.wrapped) if (w.f->group == kGroupRaymath && !w.special) v.push_back(&w);
|
|
return v;
|
|
}
|
|
|
|
// ---- per-struct fingerprint / sentinel functions ----------------------------------------------------------
|
|
struct StructFn {
|
|
std::string tag; // name used in mk_<tag>
|
|
std::string type; // how this TU spells the type
|
|
const StructDef* def;
|
|
};
|
|
|
|
std::vector<StructFn> structList(bool rl) const {
|
|
std::vector<StructFn> v;
|
|
for (const StructDef& s : m_.structs) {
|
|
if (s.opaque || !s.ok || !p_.enabled(s.group)) continue;
|
|
if (rl) v.push_back({s.name, "::" + s.name, &s});
|
|
else if (ty_.mapped.count(s.name)) v.push_back({s.name, ty_.mapped.at(s.name), &s});
|
|
else v.push_back({s.name, s.name, &s});
|
|
// Quaternion is raylib's alias of Vector4 and the engine's Quat: right behind it, before anything uses it
|
|
if (!rl && s.name == "Vector4" && ty_.mapped.count("Quaternion")) v.push_back({"Quaternion", "Quat", &s});
|
|
}
|
|
return v;
|
|
}
|
|
|
|
static std::string tagOf(const Res& r, bool rl) {
|
|
if (rl && r.t.base == "Quaternion") return "Vector4";
|
|
return r.t.base;
|
|
}
|
|
|
|
const EnumDef* enumOf(const std::string& name) const {
|
|
for (const EnumDef& e : m_.enums) if (e.name == name && e.ok && !e.items.empty()) return &e;
|
|
return nullptr;
|
|
}
|
|
|
|
// the type of a field/param as this TU spells it
|
|
std::string typeStr(const Res& r, bool rl, bool inStruct) const { return rl ? p_.rlType(r) : p_.gfxType(r, inStruct); }
|
|
|
|
std::string enumValue(const Res& r, int pick) const {
|
|
const EnumDef* e = enumOf(r.t.base);
|
|
return e ? std::to_string(e->items[static_cast<size_t>(pick) % e->items.size()].value) : "0";
|
|
}
|
|
|
|
// expression producing a sentinel value of type r
|
|
std::string makeExpr(const Res& r, const std::string& salt, bool rl, bool inStruct, int pick) const {
|
|
const std::string ts = typeStr(r, rl, inStruct);
|
|
if (r.t.ptr > 0) {
|
|
if (Plan::isCString(r)) return "gfxtest::makeStr(" + salt + ")";
|
|
return "gfxtest::makePtr<" + ts + ">(" + salt + ")";
|
|
}
|
|
switch (r.cat) {
|
|
case Cat::Builtin: case Cat::ExtBuiltin: return "gfxtest::makeScalar<" + ts + ">(" + salt + ")";
|
|
case Cat::Enum: {
|
|
if (!rl && !inStruct) return "int(" + enumValue(r, pick) + ")";
|
|
return "static_cast<" + ts + ">(" + enumValue(r, pick) + ")";
|
|
}
|
|
case Cat::Mapped: return "gfxtest::mk_" + tagOf(r, rl) + "(" + salt + ")";
|
|
case Cat::Struct:
|
|
if (!rl && p_.isMat4(r, inStruct)) return "gfxtest::mk_Mat4(" + salt + ")";
|
|
return "gfxtest::mk_" + tagOf(r, rl) + "(" + salt + ")";
|
|
case Cat::Callback: return rl ? "static_cast<" + ts + ">(nullptr)" : "&gfxtest::cb_" + r.t.base;
|
|
default: break;
|
|
}
|
|
return "{}";
|
|
}
|
|
|
|
std::string hashExpr(const Res& r, const std::string& e) const {
|
|
if (r.t.ptr > 0) return Plan::isCString(r) ? "gfxtest::hvStr(" + e + ")" : "gfxtest::hvPtr(" + e + ")";
|
|
if (r.cat == Cat::Callback) return "gfxtest::hvFn(" + e + ")";
|
|
return "gfxtest::hv(" + e + ")";
|
|
}
|
|
|
|
void structFns(std::ostream& o, bool rl) const {
|
|
// fingerprints and sentinels of every struct (a struct's fields are always declared before it)
|
|
for (const StructFn& sf : structList(rl)) {
|
|
const StructDef& s = *sf.def;
|
|
const bool isMatrix = s.name == "Matrix" && ty_.matrixMapped;
|
|
o << "inline void fp(Hash& h, const " << sf.type << "& v) {\n";
|
|
if (s.fields.empty()) o << " (void)h; (void)v;\n";
|
|
for (const Field& f : s.fields) {
|
|
const Res rf = ty_.classify(f.type);
|
|
std::string expr = "v." + f.name;
|
|
std::string close;
|
|
for (size_t d = 0; d < f.dims.size(); ++d) {
|
|
o << std::string(4 + 4 * d, ' ') << "for (const auto& x" << d << " : " << expr << ") {\n";
|
|
expr = "x" + std::to_string(d);
|
|
close = std::string(4 + 4 * d, ' ') + "}\n" + close;
|
|
}
|
|
const std::string ind(4 + 4 * f.dims.size(), ' ');
|
|
if (rf.t.ptr > 0) o << ind << "fpPtr(h, " << expr << ");\n";
|
|
else o << ind << "fp(h, " << expr << ");\n";
|
|
o << close;
|
|
}
|
|
o << "}\n";
|
|
o << "inline " << sf.type << " mk_" << sf.tag << "(int salt) {\n " << sf.type << " r{};\n";
|
|
if (s.fields.empty()) o << " (void)salt;\n";
|
|
int j = 0;
|
|
for (size_t fi = 0; fi < s.fields.size(); ++fi) {
|
|
const Field& f = s.fields[fi];
|
|
const Res rf = ty_.classify(f.type);
|
|
const int jj = isMatrix ? ty_.matrixIndex[fi] : j;
|
|
std::string lhs = "r." + f.name;
|
|
std::string saltE = "(salt * 16 + " + std::to_string(jj) + ")";
|
|
std::string close;
|
|
for (size_t d = 0; d < f.dims.size(); ++d) {
|
|
o << std::string(4 + 4 * d, ' ') << "for (std::size_t i" << d << " = 0; i" << d << " < " << f.dims[d] << "; ++i" << d << ") {\n";
|
|
lhs += "[i" + std::to_string(d) + "]";
|
|
saltE = "(" + saltE + " + static_cast<int>(i" + std::to_string(d) + ") * 3)";
|
|
close = std::string(4 + 4 * d, ' ') + "}\n" + close;
|
|
}
|
|
const std::string ind(4 + 4 * f.dims.size(), ' ');
|
|
o << ind << lhs << " = " << makeExpr(rf, saltE + " % 100003", rl, true, jj) << ";\n" << close;
|
|
++j;
|
|
}
|
|
o << " return r;\n}\n";
|
|
}
|
|
if (!rl && ty_.matrixMapped) {
|
|
o << "inline void fp(Hash& h, const eng::Mat4& v) {\n";
|
|
for (int idx : ty_.matrixIndex) o << " fp(h, v.m[" << idx << "]);\n";
|
|
o << "}\ninline eng::Mat4 mk_Mat4(int salt) {\n eng::Mat4 r{};\n for (int i = 0; i < 16; ++i) r.m[i] = makeScalar<float>((salt * 16 + i) % 100003);\n return r;\n}\n";
|
|
}
|
|
o << "\n";
|
|
}
|
|
|
|
// ---- mock of one raylib/rlgl function --------------------------------------------------------------------
|
|
int saltOf(const Wrapped& w) const { return static_cast<int>(&w - p_.wrapped.data()) * 100 + 100; }
|
|
|
|
void mockFunction(std::ostream& o, const Wrapped& w) const {
|
|
const int base = saltOf(w);
|
|
o << p_.rlType(w.ret) << " " << w.f->name << "(";
|
|
for (size_t i = 0; i < w.p.size(); ++i) o << (i ? ", " : "") << p_.rlType(w.p[i]) << " a" << i;
|
|
const bool needsCall = !w.p.empty() || w.ret.cat != Cat::Void;
|
|
o << ") {\n " << (needsCall ? "gfxtest::Call& c = " : "") << "gfxtest::begin(\"" << w.f->name << "\");\n";
|
|
if (!w.p.empty()) {
|
|
o << " c.args = {";
|
|
for (size_t i = 0; i < w.p.size(); ++i) o << (i ? ", " : "") << hashExpr(w.p[i], "a" + std::to_string(i));
|
|
o << "};\n";
|
|
}
|
|
if (w.ret.cat != Cat::Void) {
|
|
const std::string salt = std::to_string(base + 99);
|
|
const std::string rt = p_.rlType(w.ret);
|
|
if (Plan::isCString(w.ret)) o << " const char* r = gfxtest::makeStr(" << salt << ");\n";
|
|
else o << " " << rt << " r = " << makeExpr(w.ret, salt, true, false, 1) << ";\n";
|
|
o << " c.ret = " << hashExpr(w.ret, "r") << ";\n return r;\n";
|
|
}
|
|
o << "}\n\n";
|
|
}
|
|
|
|
// ---- the test of one wrapper -----------------------------------------------------------------------------
|
|
void testFunction(std::ostream& o, const Wrapped& w, const std::string& fn) const {
|
|
const int base = saltOf(w);
|
|
o << "static void " << fn << "(gfxtest::Ctx& c) { // " << w.f->name << " -> gfx::" << w.name << "\n gfxtest::reset();\n";
|
|
for (size_t i = 0; i < w.p.size(); ++i) {
|
|
const std::string salt = std::to_string(base + static_cast<int>(i) * 10);
|
|
o << " " << p_.gfxType(w.p[i]) << " a" << i << " = " << makeExpr(w.p[i], salt, false, false, static_cast<int>(i)) << ";\n";
|
|
}
|
|
auto call = [&](bool strings) {
|
|
std::string s = "gfx::" + w.name + "(";
|
|
for (size_t i = 0; i < w.p.size(); ++i) s += (i ? ", " : "") + (strings && Plan::isCString(w.p[i]) ? "s" + std::to_string(i) : "a" + std::to_string(i));
|
|
return s + ")";
|
|
};
|
|
auto expect = [&](const std::string& resultVar) {
|
|
std::string e = " c.expectCall(\"" + w.f->name + "\", {";
|
|
for (size_t i = 0; i < w.p.size(); ++i) e += (i ? ", " : "") + hashExpr(w.p[i], "a" + std::to_string(i));
|
|
e += "}, ";
|
|
e += w.ret.cat == Cat::Void ? "false, 0" : "true, " + hashExpr(w.ret, resultVar);
|
|
return e + ");\n";
|
|
};
|
|
if (w.ret.cat == Cat::Void) o << " " << call(false) << ";\n";
|
|
else o << " const auto r = " << call(false) << ";\n";
|
|
o << expect("r");
|
|
if (w.strOverload) {
|
|
o << " gfxtest::reset();\n";
|
|
for (size_t i = 0; i < w.p.size(); ++i) if (Plan::isCString(w.p[i])) o << " const std::string s" << i << " = a" << i << ";\n";
|
|
if (w.ret.cat == Cat::Void) o << " " << call(true) << ";\n";
|
|
else o << " const auto r2 = " << call(true) << ";\n";
|
|
o << expect("r2");
|
|
}
|
|
o << "}\n\n";
|
|
}
|
|
|
|
// ---- raymath ---------------------------------------------------------------------------------------------
|
|
bool oracleTestable(const Wrapped& w) const {
|
|
for (const Res& r : w.p) {
|
|
if (r.t.ptr > 1) return false;
|
|
if (r.t.ptr == 1) {
|
|
if (r.cat == Cat::Opaque || (r.cat == Cat::Builtin && r.t.base == "void") || r.cat == Cat::Callback) return false;
|
|
if (Plan::isCString(r)) return false;
|
|
}
|
|
if (r.cat == Cat::Callback) return false;
|
|
}
|
|
if (w.ret.t.ptr > 0 || w.ret.cat == Cat::Callback) return false;
|
|
return true;
|
|
}
|
|
|
|
// Declares the arguments, calls (the raymath function directly when `rl`, else the wrapper) and appends
|
|
// [arguments, result, out-parameters] fingerprints to `hashes` (rl) / `got` (wrapper).
|
|
void oracleBody(std::ostream& o, const Wrapped& w, size_t k, bool rl) const {
|
|
const std::string ind = rl ? " " : " ";
|
|
const std::string vec = rl ? "hashes" : "got";
|
|
const int base = 900000 + static_cast<int>(k) * 100;
|
|
std::vector<std::string> argExpr;
|
|
std::vector<std::string> outs;
|
|
for (size_t i = 0; i < w.p.size(); ++i) {
|
|
const Res& r = w.p[i];
|
|
const std::string salt = std::to_string(base + static_cast<int>(i) * 10);
|
|
if (r.t.ptr == 1) {
|
|
Res pointee = r;
|
|
pointee.t.ptr = 0;
|
|
// storage behind a pointer keeps raylib's layout (a Matrix stays the mirror struct, never Mat4)
|
|
o << ind << typeStr(pointee, rl, true) << " p" << i << " = " << makeExpr(pointee, salt, rl, true, static_cast<int>(i)) << ";\n";
|
|
argExpr.push_back("&p" + std::to_string(i));
|
|
outs.push_back("p" + std::to_string(i));
|
|
} else {
|
|
o << ind << typeStr(r, rl, false) << " a" << i << " = " << makeExpr(r, salt, rl, false, static_cast<int>(i)) << ";\n";
|
|
argExpr.push_back("a" + std::to_string(i));
|
|
}
|
|
}
|
|
for (size_t i = 0; i < w.p.size(); ++i) {
|
|
const Res& r = w.p[i];
|
|
if (r.t.ptr == 1) o << ind << vec << ".push_back(gfxtest::hv(p" << i << "));\n";
|
|
else o << ind << vec << ".push_back(" << hashExpr(r, "a" + std::to_string(i)) << ");\n";
|
|
}
|
|
std::string call = rl ? "::" + w.f->name + "(" : "gfx::" + w.name + "(";
|
|
for (size_t i = 0; i < argExpr.size(); ++i) call += (i ? ", " : "") + argExpr[i];
|
|
call += ")";
|
|
if (w.ret.cat == Cat::Void) o << ind << call << ";\n";
|
|
else {
|
|
o << ind << "const auto res = " << call << ";\n";
|
|
o << ind << vec << ".push_back(" << hashExpr(w.ret, "res") << ");\n";
|
|
}
|
|
for (const std::string& out : outs) o << ind << vec << ".push_back(gfxtest::hv(" << out << "));\n";
|
|
}
|
|
};
|
|
|
|
} // namespace gen
|