We're writing unit tests in Zig now

I dunno what happened, I was just applying some FloatFree(tm) and then all
of this appeared out of nowhere!
This commit is contained in:
GenericHeroGuy 2026-03-17 19:41:46 +01:00
parent 0afba2955e
commit ffa5a92ef0
14 changed files with 65 additions and 390 deletions

1
.gitignore vendored
View file

@ -26,3 +26,4 @@ Win32_LIB_ASM_Release
/bin
/build
/CMakeUserPresets.json
/.zig-cache

27
build.zig Normal file
View file

@ -0,0 +1,27 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// this is just unit tests for now, so the root source file is the tests file
const blankart = b.addModule("blankart", .{
.root_source_file = b.path("src/tests.zig"),
.target = target,
.optimize = optimize,
});
blankart.addIncludePath(b.path("src")); // -Isrc
blankart.linkSystemLibrary("c", .{}); // -lc
blankart.addCSourceFiles(.{
.root = b.path("src"),
// anything you need to test goes here
.files = &.{ "m_fixed.c", "tables.c" },
});
const blantests = b.addTest(.{
.root_module = blankart,
});
const test_step = b.step("test", "Run tests");
test_step.dependOn(&b.addRunArtifact(blantests).step);
}

View file

@ -1661,7 +1661,6 @@ static const char *CON_LoadingStrings[LOADED_ALLDONE+1] =
"Init rendering daemon...", //LOADED_RINIT
"Init audio subsystem...", //LOADED_SINITSFXCHANNELS
"Cache HUD...", //LOADED_STINIT
"Test fixed-point arithmetic...", //LOADED_MATHINIT
"Init ACSVM...", //LOADED_ACSINIT
"Check game status...", //LOADED_DCHECKNETGAME
"Now starting..."

View file

@ -52,7 +52,6 @@ typedef enum
LOADED_RINIT,
LOADED_SINITSFXCHANNELS,
LOADED_STINIT,
LOADED_MATHINIT,
LOADED_ACSINIT,
LOADED_DCHECKNETGAME,
LOADED_ALLDONE = LOADED_DCHECKNETGAME,

View file

@ -1873,61 +1873,6 @@ void D_SRB2Main(void)
ST_Init();
CON_SetLoadingProgress(LOADED_STINIT);
CONS_Printf("FixedSqrt of 32767 fracunits: %d; FixedSqrt64: %" PRId64 "\n",
FixedSqrt(0x7FFF0000),
FixedSqrt64(0x7FFF0000));
if (FixedSqrt(0x7FFF0000) == FixedSqrt64(0x7FFF0000))
{
CONS_Printf("\x83" "32767: Test OK!" "\x80" "\n");
}
else
{
CONS_Printf("\x85" "32767: Test NG!" "\x80" "\n");
}
CONS_Printf("FixedSqrt of 4 fracunits: %d; FixedSqrt64: %" PRId64 "\n",
FixedSqrt(0x40000),
FixedSqrt64(0x40000));
if (FixedSqrt(0x40000) == FixedSqrt64(0x40000))
{
CONS_Printf("\x83" "4: Test OK!" "\x80" "\n");
}
else
{
CONS_Printf("\x85" "4: Test NG!" "\x80" "\n");
}
// You should probably generate the weird number with RANDOM.org
#define WEIRDNUMBER 3886284 /* 59.3 fracunits; this should approximate to around 7 */
CONS_Printf("FixedSqrt of 59.3 fracunits: %d; FixedSqrt64: %" PRId64 "\n",
FixedSqrt(WEIRDNUMBER),
FixedSqrt64(WEIRDNUMBER));
if (FixedSqrt(WEIRDNUMBER) == FixedSqrt64(WEIRDNUMBER))
{
CONS_Printf("\x83" "59.3: Test OK!" "\x80" "\n");
}
else
{
CONS_Printf("\x85" "59.3: Test NG!" "\x80" "\n");
}
#undef WEIRDNUMBER
CONS_Printf("FixedSqrt64 of 65535 fracunits: %" PRId64 "(not possible at 32-bit scale)\n",
FixedSqrt64(0xFFFFFFFF));
CONS_Printf("IntSqrt of 32767: %d; IntSqrt64: %" PRId64 "\n",
IntSqrt(0x7FFF),
IntSqrt64(0x7FFF));
CONS_Printf("IntSqrt of 4: %d; IntSqrt64: %" PRId64 "\n",
IntSqrt(4),
IntSqrt64(4));
CON_SetLoadingProgress(LOADED_MATHINIT);
CONS_Printf("ACS_Init(): Init Action Code Script VM.\n");
ACS_Init();
CON_SetLoadingProgress(LOADED_ACSINIT);

View file

@ -509,16 +509,8 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush)
static INT32 K_KartGetSMonitorOdds(UINT32 dist)
{
// I'm tired; use floating-point distances for this fuckshit.
INT32 monitordist = SMONITORDIST;
float fac_f = (float)(dist) / ((float)(monitordist));
if (fac_f < 1.0f)
return 0;
// If you're far enough for this to be in your item slot, you're far enough to SERIOUSLY need this.
return MAXSMONITORODDS;
return dist >= (UINT32)SMONITORDIST ? MAXSMONITORODDS : 0;
}
// updates all result cooldown timers, and sets cooldowns for "unique" items

View file

@ -278,7 +278,7 @@ extern boolean forcefullinvintheme;
boolean K_PlayFullInvinTheme(void);
void K_DoInvincibility(player_t *player, tic_t time);
void K_DoSMonitor(player_t *player, tic_t time);
#define SMONITORWARNINGTIME (fixed_t)(0.833333333f * (float)(FRACUNIT))
#define SMONITORWARNINGTIME (5*FRACUNIT/6)
fixed_t K_SMonitorGradient(UINT16 time);
fixed_t K_GetSMonitorSpeed(UINT16 time);
fixed_t K_GetSMonitorAccel(UINT16 time);

View file

@ -11,30 +11,12 @@
/// \file m_fixed.c
/// \brief Fixed point implementation
#if 0 //#ifndef NO_M
#include <math.h>
#define HAVE_SQRT
#if 0 //#ifndef _WIN32 // MSVCRT does not have *f() functions
#define HAVE_SQRTF
#endif
#endif
#include "doomdef.h"
#include "m_fixed.h"
#include "tables.h" // ANGLETOFINESHIFT
fixed_t FixedSqrt(fixed_t x)
{
#ifdef HAVE_SQRT
const float fx = FIXED_TO_FLOAT(x);
float fr;
#ifdef HAVE_SQRTF
fr = sqrtf(fx);
#else
fr = (float)sqrt(fx);
#endif
return FLOAT_TO_FIXED(fr);
#else
// The neglected art of Fixed Point arithmetic
// Jetro Lauha
// Seminar Presentation
@ -58,22 +40,11 @@ fixed_t FixedSqrt(fixed_t x)
}
} while (count-- != 0);
return root;
#endif
}
// Shitty 64-bit duplicate so certain distancing edgecases in k_odds die
INT64 FixedSqrt64(INT64 x)
{
#ifdef HAVE_SQRT
const float fx = FixedToFloat64(x);
float fr;
#ifdef HAVE_SQRTF
fr = sqrtf(fx);
#else
fr = (float)sqrt(fx);
#endif
return FloatToFixed64(fr);
#else
// The neglected art of Fixed Point arithmetic
// Jetro Lauha
// Seminar Presentation
@ -97,7 +68,6 @@ INT64 FixedSqrt64(INT64 x)
}
} while (count-- != 0);
return root;
#endif
}
fixed_t FixedHypot(fixed_t x, fixed_t y)
@ -1242,231 +1212,3 @@ fixed_t ApproachFixed(fixed_t current, fixed_t target, fixed_t inc, fixed_t dec)
}
return current;
}
/** \brief Returns the floating-point value 'current' after it tries to approach target, going up at
most 'inc' and going down at most 'dec'.
\param current (float) current value
\param target (float) target value
\param inc (float) value to increment by
\param dec (float) value to decrement by
\return (float) current value after incrementing/decrementing
*/
float ApproachFloat(float current, float target, float inc, float dec)
{
if (current < target)
{
current += inc;
if (current > target)
{
current = target;
}
}
else
{
current -= dec;
if (current < target)
{
current = target;
}
}
return current;
}
/** \brief Returns the double-precision floating-point value 'current' after it tries to approach
target, going up at most 'inc' and going down at most 'dec'.
\param current (double) current value
\param target (double) target value
\param inc (double) value to increment by
\param dec (double) value to decrement by
\return (double) current value after incrementing/decrementing
*/
double ApproachDouble(double current, double target, double inc, double dec)
{
if (current < target)
{
current += inc;
if (current > target)
{
current = target;
}
}
else
{
current -= dec;
if (current < target)
{
current = target;
}
}
return current;
}
#ifdef M_TESTCASE
//#define MULDIV_TEST
#define SQRT_TEST
static inline void M_print(INT64 a)
{
const fixed_t w = (a >> FRACBITS);
fixed_t f = a % FRACUNIT;
fixed_t d = FRACUNIT;
if (f == 0)
{
printf("%d", (fixed_t)w);
return;
}
else while (f != 1 && f / 2 == f >> 1)
{
d /= 2;
f /= 2;
}
if (w == 0)
printf("%d/%d", (fixed_t)f, d);
else
printf("%d+(%d/%d)", (fixed_t)w, (fixed_t)f, d);
}
FUNCMATH FUNCINLINE static inline fixed_t FixedMulC(fixed_t a, fixed_t b)
{
return (fixed_t)((((INT64)a * b)) / FRACUNIT);
}
FUNCMATH FUNCINLINE static inline INT64 FixedMul64C(fixed_t a, fixed_t b)
{
return (INt64)((((INT64)a * b)) / FRACUNIT);
}
FUNCMATH FUNCINLINE static inline fixed_t FixedDivC2(fixed_t a, fixed_t b)
{
INT64 ret;
if (b == 0)
I_Error("FixedDiv: divide by zero");
ret = (((INT64)a * FRACUNIT)) / b;
if ((ret > INT32_MAX) || (ret < INT32_MIN))
I_Error("FixedDiv: divide by zero");
return (fixed_t)ret;
}
FUNCMATH FUNCINLINE static inline fixed_t FixedDivC(fixed_t a, fixed_t b)
{
if ((abs(a) >> (FRACBITS - 2)) >= abs(b))
return (a^b) < 0 ? INT32_MIN : INT32_MAX;
return FixedDivC2(a, b);
}
FUNCMATH FUNCINLINE static inline fixed_t FixedSqrtC(fixed_t x)
{
const float fx = FIXED_TO_FLOAT(x);
float fr;
#ifdef HAVE_SQRTF
fr = sqrtf(fx);
#else
fr = (float)sqrt(fx);
#endif
return FLOAT_TO_FIXED(fr);
}
int main(int argc, char** argv)
{
int n = 10;
INT64 a, b;
fixed_t c, d;
(void)argc;
(void)argv;
#ifdef MULDIV_TEST
for (a = 1; a <= INT32_MAX; a += FRACUNIT)
for (b = 0; b <= INT32_MAX; b += FRACUNIT)
{
c = FixedMul(a, b);
d = FixedMulC(a, b);
if (c != d)
{
printf("(");
M_print(a);
printf(") * (");
M_print(b);
printf(") = (");
M_print(c);
printf(") != (");
M_print(d);
printf(") \n");
n--;
printf("%d != %d\n", c, d);
}
c = FixedDiv(a, b);
d = FixedDivC(a, b);
if (c != d)
{
printf("(");
M_print(a);
printf(") / (");
M_print(b);
printf(") = (");
M_print(c);
printf(") != (");
M_print(d);
printf(")\n");
n--;
printf("%d != %d\n", c, d);
}
if (n <= 0)
exit(-1);
}
#endif
#ifdef SQRT_TEST
for (a = 0; a <= INT32_MAX; a += 1)
{
c = FixedSqrt(a);
d = FixedSqrtC(a);
b = abs(c - d);
if (b > 1)
{
printf("sqrt(");
M_print(a);
printf(") = {(");
M_print(c);
printf(") != (");
M_print(d);
printf(")} \n");
//n--;
printf("%d != %d {", c, d);
M_print(b);
printf("}\n");
}
if (n <= 0)
exit(-1);
}
#endif
exit(0);
}
static void *cpu_cpy(void *dest, const void *src, size_t n)
{
return memcpy(dest, src, n);
}
void *(*memcpy)(void* dest, const void* src, size_t n) = cpu_cpy;
void I_Error(const char *error, ...)
{
(void)error;
exit(-1);
}
#endif

View file

@ -57,21 +57,11 @@ FUNCMATH FUNCINLINE static ATTRINLINE float FixedToFloat(fixed_t x)
return x / (float)FRACUNIT;
}
FUNCMATH FUNCINLINE static ATTRINLINE float FixedToFloat64(INT64 x)
{
return (float)x / (float)FRACUNIT;
}
FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FloatToFixed(float f)
{
return (fixed_t)(f * FRACUNIT);
}
FUNCMATH FUNCINLINE static ATTRINLINE INT64 FloatToFixed64(float f)
{
return (INT64)(f * FRACUNIT);
}
/*!
* \brief convert fixed_t into double-precision floating number
*/
@ -469,8 +459,6 @@ void FM_Scale(matrix_t *dest, fixed_t x, fixed_t y, fixed_t z);
INT32 ApproachINT32(INT32 current, INT32 target, INT32 inc, INT32 dec);
UINT32 ApproachUINT32(UINT32 current, UINT32 target, UINT32 inc, UINT32 dec);
fixed_t ApproachFixed(fixed_t current, fixed_t target, fixed_t inc, fixed_t dec);
float ApproachFloat(float current, float target, float inc, float dec);
double ApproachDouble(double current, double target, double inc, double dec);
/** \brief Returns the INT32 value ``current`` after it tries to approach ``target``, going up
or down at most ``delta``.
@ -505,8 +493,6 @@ double ApproachDouble(double current, double target, double inc, double dec);
#define ApproachOneWayUINT32(current, target, delta) (ApproachUINT32(current, target, delta, delta))
#define ApproachOneWayFixed(current, target, delta) (ApproachFixed(current, target, delta, delta))
#define ApproachOneWayFloat(current, target, delta) (ApproachFloat(current, target, delta, delta))
#define ApproachOneWayDouble(current, target, delta) (ApproachDouble(current, target, delta, delta))
#ifdef __cplusplus
} // extern "C"

35
src/tests.zig Normal file
View file

@ -0,0 +1,35 @@
const std = @import("std");
const C = @cImport({
// include the headers you need for testing here
// also make sure to add the corresponding source files in build.zig until
// the symbol errors go away :face_holding_back_tears:
@cInclude("m_fixed.h");
});
const fixed_t = i32;
const FRACUNIT: fixed_t = 65536;
const FRACBITS = 16;
test "FixedSqrt d_main.cpp"
{
try std.testing.expectEqual(C.FixedSqrt(0x7FFF0000), C.FixedSqrt64(0x7FFF0000));
try std.testing.expectEqual(C.FixedSqrt(0x40000), C.FixedSqrt64(0x40000));
// You should probably generate the weird number with RANDOM.org
const WEIRDNUMBER = 3886284; // 59.3 fracunits; this should approximate to around 7
try std.testing.expectEqual(C.FixedSqrt(WEIRDNUMBER), C.FixedSqrt64(WEIRDNUMBER));
}
test "FixedSqrt m_fixed.c"
{
var a: i64 = 0;
// not enough time in the world for all 2 billion values...
// prime numbers to the rescue
while (a <= std.math.maxInt(i32)) : (a += 97) {
const c: fixed_t = C.FixedSqrt(@intCast(a));
const d: fixed_t = @intFromFloat(@sqrt(@as(f64, @floatFromInt(a)) / FRACUNIT) * FRACUNIT);
try std.testing.expect(@abs(c - d) <= 1);
}
}

View file

@ -1,5 +0,0 @@
target_sources(srb2tests PRIVATE
boolcompat.cpp
notnull.cpp
testbase.hpp
)

View file

@ -1,9 +0,0 @@
#include "testbase.hpp"
#include <catch2/catch_test_macros.hpp>
#include "../doomtype.h"
TEST_CASE("C++ bool is convertible to doomtype.h boolean") {
REQUIRE(static_cast<boolean>(true) == 1);
REQUIRE(static_cast<boolean>(false) == 0);
}

View file

@ -1,31 +0,0 @@
#include "testbase.hpp"
#include <catch2/catch_test_macros.hpp>
#include "../cxxutil.hpp"
namespace {
class A {
public:
virtual bool foo() = 0;
};
class B : public A {
public:
virtual bool foo() override final { return true; };
};
} // namespace
TEST_CASE("NotNull<int*> is constructible from int*") {
int a = 0;
REQUIRE(srb2::NotNull(static_cast<int*>(&a)));
}
TEST_CASE("NotNull<A*> is constructible from B* where B inherits from A") {
B b;
REQUIRE(srb2::NotNull(static_cast<B*>(&b)));
}
TEST_CASE("NotNull<A*> dereferences to B& to call foo") {
B b;
srb2::NotNull<A*> a {static_cast<B*>(&b)};
REQUIRE(a->foo());
}

View file

@ -1,6 +0,0 @@
#ifndef __SRB2_TESTS_TESTBASE_HPP__
#define __SRB2_TESTS_TESTBASE_HPP__
#define SRB2_ASSERT_HANDLER srb2::NoOpAssertHandler
#endif // __SRB2_TESTS_TESTBASE_HPP__