Skip to content

BasicMathTestSuite: Refactor from its current "one fact per function" format into the granular "one fact per scenario" #329

Closed

Description

@YYDan

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

Before.txt

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

  1. self-assigned this
    on Jul 27, 2026
  2. added
    refactorAn improvement to existing test/module code
    on Jul 27, 2026
  3. added theissue type on Jul 27, 2026
  4. YYDan commented on Aug 12, 2026

    @YYDan
    ContributorAuthor

    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)

    Test Before After
    math_set_epsilon_test 1 4
    ceil_test 1 2
    floor_test 1 2
    math_get_epsilon_test 1 2

    Phase 2 — trig basics (8 → 29)

    Test Before After
    tan_test 1 7
    arccos_test (incl. arccos_input_range_test) 2 5
    arcsin_test (incl. arcsin_input_range_test) 2 5
    arctan_test 1 4
    cos_test 1 4
    sin_test 1 4

    Phase 3 — degree trig (9 → 41)

    Test Before After
    darctan2_test 1 12
    dtan_test 1 7
    darccos_test (incl. darccos_input_range_test) 2 5
    darcsin_test (incl. darcsin_input_range_test) 2 5
    darctan_test 1 4
    dcos_test 1 4
    dsin_test 1 4

    Additional — arctan2_test (1 → 13)

    arctan2_test appears 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 alongside darctan2_test (4 quadrant cases + 9 known-value cases).


    Conventions applied

    • Split rule: one fact per distinct input value, plus one fact per property/range loop. This overshoots the estimated counts in several places (tan_test 7 vs 4, darctan2_test 12 vs 4) — deliberate, since the point is granular pass/fail rather than hitting the planning estimate.
    • Section header comment per family (// DARCCOS TESTS), facts named <original>_test #N.
    • #macro declarations hoisted to section level — GML macros are global at compile time, so a macro left inside a split fact body becomes a duplicate definition.
    • Setup duplicated into each fact so facts are self-contained; commented-out show_debug_message cruft stripped.
    • Original assert messages preserved verbatim, including their #N prefixes (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:

    • Assert calls: 703 → 703 suite-wide — none lost.
    • Assert content (balanced-paren extraction, normalised, sorted): identical except three deliberate message-text fixes, listed below.
    • Bracket balance (string/comment-aware): curly/paren/square all zero, matching HEAD.
    • All 53 original base names still present; no per-function assert-count mismatch; no duplicate fact names.

    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)

    1. floor_test — two asserts both labelled #1 Local Negative; second renumbered to #2.
    2. darctan_test #3 — "#3 (darctan(-1) == -45" → stray open paren removed.
    3. arctan2_test #4 — "#4 arctan2(-2, -2) == -(pi - (pi/4)" → missing closing paren added.

    Also noted

    two_pi_Arctan2Test is 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.

  5. YYDan commented on Aug 12, 2026

    @YYDan
    ContributorAuthor

    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

    Test Before After Estimate Asserts
    degtorad_test 1 10 3 10
    radtodeg_test 1 10 3 10
    lengthdir_x_test 1 3 3 3
    lengthdir_y_test 1 2 3 2
    angle_difference_test 1 23 4 32
    Total 5 48 16 57

    Running total after Phase 4: 167 facts.

    angle_difference_test is the one place we deliberately did not go one-fact-per-assert. Its 32 asserts sit inside shared buffer_create/buffer_write/buffer_delete blocks, 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_test lands under its estimate at 2 — it only ever had 2 asserts.

    Phase 5 — Dot products

    Test Before After Estimate Asserts
    dot_product_test 1 7 4 7
    dot_product_3d_test 1 12 4 12
    dot_product_normalised_test 1 12 4 12
    dot_product_3d_normalised_test 1 12 4 12
    Total 4 43 16 43

    Running 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

    Test Before After Estimate Asserts
    point_direction_test 1 7 4 10
    point_distance_3d_test 1 7 4 7
    point_distance_test 1 7 4 7
    Total 3 21 12 24

    Running 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. In point_direction_test the 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:

    Check Result
    Total assert calls 703 → 703
    Per-test assert counts no mismatches
    Assert content (normalised, sorted diff) identical except 3 deliberate typo fixes
    Bracket balance, string/comment-aware () {} [] all balanced, matching HEAD
    Base names lost / added none / none
    Duplicate fact names none

    The three typo fixes are the previously-noted duplicate #1 Local Negative label in floor_test, and two unbalanced parens in assert message text (darctan_test #3, arctan2_test #4). No assertion logic was changed anywhere.

  6. YYDan commented on Aug 13, 2026

    @YYDan
    ContributorAuthor

    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)

    Test Before After
    exp_test 1 16
    ln_test 1 17
    log10_test 1 19
    log2_test 1 19
    logn_test 1 39

    All 1:1 (one fact per assert). Running total: 329, confirmed passing.

    Side finding: logn_test asserts #34–#36 are behaviourally identical to #19–#21 — they sit under a // negative values heading where every other assert negates the value, so they were clearly meant to be logn(-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)

    Test Before After
    max_test 1 24
    mean_test 1 23
    median_test 1 28
    min_test 1 24

    Twelve in-body #macro declarations 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 from max_test — instance_create_depth(0,0,0,oTest) whose result was never used, and oTest has no event scripts, so no side effects. Running total: 424, confirmed passing.

    Phase 9 — The big ones (9 → 211)

    Test Before After
    abs_test 1 16
    clamp_test 1 62
    frac_test 1 16
    lerp_test 1 24
    power_test 1 32
    round_test 1 22
    sign_test 1 16
    sqrt_test 1 7
    sqr_test 1 16

    sqrt_test wasn'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

    • Section header per family (// POWER TESTS), facts named <original> #N, original assert messages preserved verbatim including their #N prefixes and existing typos.
    • Setup duplicated inline into each fact — every fact builds its own minimal buffer, writing and reading only the types it needs, so values are bit-identical to the original shared buffer.
    • Default granularity is one fact per assert. Grouping is kept only where asserts form a single coherent scenario: property/range while loops, and the int64 precondition-plus-observation chains in abs_test.
    • Epsilon: the defensive 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

    Check Result
    Assert calls, base vs now 703 → 703
    Assert content (normalised, sorted diff) identical bar 3 message typo fixes from Phases 1–2
    Base test names 53 → 53, none lost or added
    Per-test assert-count mismatches none
    Duplicate fact names none
    Bracket balance () {} [] balanced, matching base
    In-body #macro declarations 0 of 21
    Buffer create/delete pairing per fact 0 issues
    Diff scope BasicMathTestSuite.gml only

    Assert distribution across the 626 facts: 598 single-assert, 24 with 2–4, and 4 outliers — the math_get/set_epsilon facts, which assert on epsilon state and don't split further.

    AfterPhase9.txt

  7. added a commit that references this issue on Aug 13, 2026
    2fd5351
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

refactorAn improvement to existing test/module code

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions