Repository navigation
BasicMathTestSuite: Refactor from its current "one fact per function" format into the granular "one fact per scenario" #329
Description
Description
Refactor BasicMathTestSuite from its current "one fact per function" format into the granular "one fact per scenario" format established by AdamBorek and Indibulous in summer 2024 (e.g. BasicArrayTestSuite, BasicDataTypesTestSuite).
This is required, because currently we only count 1 pass/fail per addFact() call, even though in these legacy-format suites we might be performing many individual tests which should have their own pass/fail result as part of that 1. Accordingly, our test count is way lower than reality and we're not able to be more granular which individual tests passed/failed. This refactor will fix all of this.
This refactor allows the existing tests to be more cleanly separated/siloed and therefore easier to maintain later on, plus to add more tests to fill coverage holes in subsequent issues.
Current state
- 4,019 lines, 52 addFact() calls
- Each fact tests one GM function (e.g. abs_test, sin_test, sqrt_test)
- Inside each fact, multiple { } blocks test sub-scenarios (whole numbers, fractionals, literals, int32, int64, etc.)
Example: abs_test has 5 sub-scenarios packed into one fact with ~15 asserts
Target state
- Each sub-scenario becomes its own addFact() with a unique #N name
- Section comment header above each group (e.g. // ABSOLUTE VALUE TESTS)
- Each fact is self-contained: setup one logical check assert
Expected growth: 52 facts to become ~120–150 facts, ~4,000 lines to become ~4,500 lines
Functions to split (with estimated new fact count)
Function Name Before After
abs_test 1 5
angle_difference_test 1 4
arccos_test 2 4
arcsin_test 2 4
arctan2_test 1 4
arctan_test 1 4
ceil_test 1 3
clamp_test 1 12
cos_test 1 4
darccos_test 2 4
darcsin_test 2 4
darctan2_test 1 4
darctan_test 1 4
dcos_test 1 4
degtorad_test 1 3
dot_product_3d_normalised_test 1 4
dot_product_3d_test 1 4
dot_product_normalised_test 1 4
dot_product_test 1 4
dsin_test 1 4
dtan_test 1 4
exp_test 1 5
floor_test 1 3
frac_test 1 3
lengthdir_x_test 1 3
lengthdir_y_test 1 3
lerp_test 1 7
ln_test 1 5
log10_test 1 5
log2_test 1 5
logn_test 1 13
math_get_epsilon_test 1 2
math_set_epsilon_test 1 4
max_test 1 5
mean_test 1 5
median_test 1 5
min_test 1 5
point_direction_test 1 4
point_distance_3d_test 1 4
point_distance_test 1 4
power_test 1 5
radtodeg_test 1 3
round_test 1 3
sign_test 1 3
sin_test 1 4
sqrt_negative_num_test 1 2
sqrt_test 1 3
sqr_test 1 3
tan_test 1 4
Total 52 ~130
Notes
- No async tests, no resource management, no loops — pure function tests. Safe to refactor mechanically.
- Assert messages already use #N numbering; just move them into the new fact names.
- The math_get_epsilon_test / math_set_epsilon_test functions (2 facts, 6 sub-scenarios) are a good starting point — small enough to verify the pattern quickly before tackling the larger functions like clamp_test (12 sub-scenarios) or logn_test (13 sub-scenarios).
Script Name
BasicMathTestSuite
Steps To The Task
- Group by function — the file is already organized this way. List each function and its sub-scenarios.
- Extract each { } block (or logical grouping of asserts) into its own addFact():
- Name: function_name test #N (e.g. abs_test added workflows #1, abs_test Ci main #2)
- Preserve the original //#N comment as the fact's inline description
- Move all setup variables inside the new fact
- Move the assert message to match the fact name
- Add section headers — // ABSOLUTE VALUE TESTS, // TRIGONOMETRIC TESTS, etc.
- Verify — run the suite and confirm all tests pass (same coverage, just more granular)
Proposed approach
- Phase 1 — Verify the pattern: math_get_epsilon_test, math_set_epsilon_test, ceil_test, floor_test (small, simple)
- Phase 2 — Trig basics: sin_test, cos_test, tan_test, arcsin_test, arccos_test, arctan_test
- Phase 3 — Degree trig: dsin_test, dcos_test, dtan_test, darcsin_test, darccos_test, darctan_test, darctan2_test
- Phase 4 — Conversion & directional: degtorad_test, radtodeg_test, lengthdir_x_test, lengthdir_y_test, angle_difference_test
- Phase 5 — Dot products: dot_product_* (4 functions)
- Phase 6 — Point functions: point_direction_test, point_distance_test, point_distance_3d_test
- Phase 7 — Logarithmic: ln_test, log10_test, log2_test, logn_test, exp_test
- Phase 8 — Aggregation: max_test, min_test, mean_test, median_test
- Phase 9 — The big ones: abs_test, clamp_test, lerp_test, power_test, sqr_test, round_test, frac_test, sign_test
Activity
YYDan commentedon Aug 12, 2026
Progress update — Phases 1–3 complete (+
arctan2_test)53 → 124 facts. No test logic changed; every original assert is preserved.
Phase 1 — verify the pattern (4 → 10)
math_set_epsilon_testceil_testfloor_testmath_get_epsilon_testPhase 2 — trig basics (8 → 29)
tan_testarccos_test(incl.arccos_input_range_test)arcsin_test(incl.arcsin_input_range_test)arctan_testcos_testsin_testPhase 3 — degree trig (9 → 41)
darctan2_testdtan_testdarccos_test(incl.darccos_input_range_test)darcsin_test(incl.darcsin_input_range_test)darctan_testdcos_testdsin_testAdditional —
arctan2_test(1 → 13)arctan2_testappears in the per-function table above but wasn't listed in any phase, so it would have been missed. Folded into the Phase 3 pass and split alongsidedarctan2_test(4 quadrant cases + 9 known-value cases).Conventions applied
tan_test7 vs 4,darctan2_test12 vs 4) — deliberate, since the point is granular pass/fail rather than hitting the planning estimate.// DARCCOS TESTS), facts named<original>_test #N.#macrodeclarations hoisted to section level — GML macros are global at compile time, so a macro left inside a split fact body becomes a duplicate definition.show_debug_messagecruft stripped.#Nprefixes (which no longer always match the new fact number).Epsilon handling
Facts that opened with a defensive
math_set_epsilon(0.00001)have that call replicated into each new fact rather than shared. This multiplies the calls, but keeps each fact self-contained and avoids depending on the unmerged epsilon set/restore fix (see #332). Those calls should be cleaned up wholesale when that lands.Verification
Static, per phase, against
HEAD:Runtime: all 80 facts passing after Phases 1–2. Log for the current 124 attached.
AfterPhase3.txt
Deviations from verbatim (message text only, no logic change)
floor_test— two asserts both labelled#1 Local Negative; second renumbered to#2.darctan_test #3—"#3 (darctan(-1) == -45"→ stray open paren removed.arctan2_test #4—"#4 arctan2(-2, -2) == -(pi - (pi/4)"→ missing closing paren added.Also noted
two_pi_Arctan2Testis declared but never referenced anywhere in the repo. Hoisted along with the used macro rather than deleted, as removing dead code is outside this refactor's scope.Remaining
Phases 4–9: conversion & directional, dot products, point functions, logarithmic, aggregation, and the big ones.
YYDan commentedon Aug 12, 2026
Phases 4–6 complete — conversion & directional, dot products, point functions
Suite total is now 224 facts, up from 53 at the start of the refactor (124 after Phase 3).
Phase 4 — Conversion & directional
degtorad_testradtodeg_testlengthdir_x_testlengthdir_y_testangle_difference_testRunning total after Phase 4: 167 facts.
angle_difference_testis the one place we deliberately did not go one-fact-per-assert. Its 32 asserts sit inside sharedbuffer_create/buffer_write/buffer_deleteblocks, and the four asserts within each block are a single sign-handling matrix over one input pair rather than four independent scenarios. Splitting per assert would have meant duplicating buffer setup ~190 extra lines for no extra diagnostic value, so it's one fact per type (real / int / int64 / mixed pairs). Each fact builds its own minimal buffer, writing and reading only the values it needs, so every value read is identical to before.lengthdir_y_testlands under its estimate at 2 — it only ever had 2 asserts.Phase 5 — Dot products
dot_product_testdot_product_3d_testdot_product_normalised_testdot_product_3d_normalised_testRunning total after Phase 5: 206 facts.
One fact per assert throughout — each of these was already one distinct input scenario per assert.
Phase 6 — Point functions
point_direction_testpoint_distance_3d_testpoint_distance_testRunning total after Phase 6: 224 facts.
Same shared-buffer shape as
angle_difference, so the same per-type rule applies: real / int / int64 / three mixed-type pairs / zero-case = 7 facts each. Inpoint_direction_testthe direct and negated-target asserts stay paired within their type fact (same buffer, same input pair, opposite sign).Verification
Static verification battery run after each phase, comparing against
HEAD:(){}[]all balanced, matchingHEADThe three typo fixes are the previously-noted duplicate
#1 Local Negativelabel infloor_test, and two unbalanced parens in assert message text (darctan_test #3,arctan2_test #4). No assertion logic was changed anywhere.YYDan commentedon Aug 13, 2026
Phases 7–9 complete — suite now at 626 facts
Three phases since the last update, taking the suite from 224 to 626 facts with no change to assertion logic.
Phase 7 — Logarithmic (5 → 110)
exp_testln_testlog10_testlog2_testlogn_testAll 1:1 (one fact per assert). Running total: 329, confirmed passing.
Side finding:
logn_testasserts #34–#36 are behaviourally identical to #19–#21 — they sit under a// negative valuesheading where every other assert negates the value, so they were clearly meant to belogn(-2, -_vReal)and instead repeat the earlier negative-base cases. Preserved verbatim here per the refactor's no-logic-changes rule; written up separately. Issue #334 added for this.Phase 8 — Aggregation (4 → 99)
max_testmean_testmedian_testmin_testTwelve in-body
#macrodeclarations hoisted to section level (GML macros are global at compile time, so leaving them inside a split fact is a duplicate-definition error). One dead statement dropped frommax_test—instance_create_depth(0,0,0,oTest)whose result was never used, andoTesthas no event scripts, so no side effects. Running total: 424, confirmed passing.Phase 9 — The big ones (9 → 211)
abs_testclamp_testfrac_testlerp_testpower_testround_testsign_testsqrt_testsqr_testsqrt_testwasn't on the phase list but was still a single 7-assert fact, so it's folded in — the file now has no unrefactored multi-assert tests. Running total: 626.Conventions applied throughout
// POWER TESTS), facts named<original> #N, original assert messages preserved verbatim including their#Nprefixes and existing typos.whileloops, and the int64 precondition-plus-observation chains inabs_test.math_set_epsilon(0.00001)is replicated into each new fact rather than blocking on the epsilon set/restore fix. That adds call sites for the epsilon issue to sweep up later, but keeps facts self-contained and independent of an unmerged change.Verification
(){}[]#macrodeclarationsBasicMathTestSuite.gmlonlyAssert distribution across the 626 facts: 598 single-assert, 24 with 2–4, and 4 outliers — the
math_get/set_epsilonfacts, which assert on epsilon state and don't split further.AfterPhase9.txt
Fixes #329 - BasicMathTestSuite split correctly