From 0091d0270445b0a099f96d63823790eea3b60729 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Tue, 28 Apr 2026 13:04:52 -0400 Subject: [PATCH] refactor: rewrite test_orbital_period with SCENARIO/SECTION pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Consolidate 3 TEST_CASEs into 2 SCENARIOs with SECTIONs - Tighten tolerances: Earth 365.2105±0.1d, Mars 670.9345±0.1d - Direction test: verify prograde rotation with 1μrad precision - Use analytical propagation values as ground truth - Add TOML 1.0 compliant configs (single-line inline tables) - Update continue.md with tooling notes and TOML syntax requirements --- continue.md | 34 +++++++++++ tests/test_energy.toml | 12 +--- tests/test_orbital_period.cpp | 103 +++++++++++++++------------------ tests/test_orbital_period.toml | 18 +----- 4 files changed, 87 insertions(+), 80 deletions(-) create mode 100644 continue.md diff --git a/continue.md b/continue.md new file mode 100644 index 0000000..c2f61d9 --- /dev/null +++ b/continue.md @@ -0,0 +1,34 @@ +# Test Refactoring Optimization Strategy + +## Tooling +- `scripts/sim_engine.py` — Generic orbital mechanics simulator (Python, TOML 1.0 configs) + - Replicates C++ physics: Kepler propagation, orbital↔Cartesian transforms, drift detection + - Multi-body hierarchical propagation with global/local coordinate tracking + - Use for precalculating expected values (transition times, final states, energy conservation) +- TOML configs in `tests/` must use TOML 1.0 inline table syntax (single-line `{}`) + - Old configs in `old_tests/` use multiline inline tables (toml-c17 style) — keep for reference + - Python's `tomllib` requires single-line inline tables + +## 1. Structure +- One `SCENARIO("description")` per logical test group, with `[tag1][tag2]` annotations +- Shared fixture: all constants, structs, and variables declared between `SCENARIO` opening and first `SECTION` +- Precompute expected values analytically at fixture level (use `scripts/*.py` for complex simulations) + +## 2. Duplication elimination +- Use lambdas that capture the fixture for repeated setup→call→assert patterns +- Reuse shared structs in-place (mutate fields rather than recreating) +- Single-line `SECTION`s when body is one statement: `SECTION("name") { helper(arg); }` + +## 3. Assertions +- `using Catch::Matchers::WithinAbs;` after includes +- `REQUIRE_THAT(value, WithinAbs(expected, tolerance))` — never `Approx()` +- Tolerances based on actual observed errors, tightened aggressively (1e-12 for angles, 1e-6 for meters, etc.) +- Replace qualitative checks (`a > b`) with quantitative (`WithinAbs(expected, tol)`) +- `INFO("label: " << value)` for debugging context + +## 4. Process per file +- Process **one test file at a time**. +- For each file, **discuss with the user** how to precompute `REQUIRE` values — analytical formulas, Python scripts, or simulation runs. +- Copy from `old_tests/` to `tests/`, rewrite, rebuild, verify. +- **Always ask for review** before moving to the next file. +- **Only commit when asked.** diff --git a/tests/test_energy.toml b/tests/test_energy.toml index 1b7dc41..bc18560 100644 --- a/tests/test_energy.toml +++ b/tests/test_energy.toml @@ -8,11 +8,7 @@ mass = 1.989e30 radius = 6.96e8 parent_index = -1 color = { r = 1.0, g = 1.0, b = 0.0 } -orbit = { - semi_major_axis = 0.0, - eccentricity = 0.0, - true_anomaly = 0.0 -} +orbit = { semi_major_axis = 0.0, eccentricity = 0.0, true_anomaly = 0.0 } [[bodies]] name = "Earth" @@ -20,8 +16,4 @@ mass = 5.972e24 radius = 6.371e6 parent_index = 0 color = { r = 0.0, g = 0.5, b = 1.0 } -orbit = { - semi_major_axis = 1.496e11, - eccentricity = 0.0, - true_anomaly = 0.0 -} +orbit = { semi_major_axis = 1.496e11, eccentricity = 0.0, true_anomaly = 0.0 } diff --git a/tests/test_orbital_period.cpp b/tests/test_orbital_period.cpp index dab10aa..c399a88 100644 --- a/tests/test_orbital_period.cpp +++ b/tests/test_orbital_period.cpp @@ -1,81 +1,73 @@ #include +#include #include "../src/physics.h" #include "../src/simulation.h" #include "../src/config_loader.h" #include "../src/test_utilities.h" #include -TEST_CASE("Orbital period - Earth (RK4)", "[period][rk4]") { +using Catch::Matchers::WithinAbs; + +SCENARIO("Orbital period measurement with analytical propagation", + "[period][analytical]") { const double TIME_STEP = 60.0; - const double EXPECTED_PERIOD_DAYS = 365.0; const double SECONDS_PER_DAY = 86400.0; - const double MAX_SIMULATION_DAYS = 400.0; SimulationState* sim = create_simulation(10, 0, 0, TIME_STEP); - REQUIRE(load_system_config(sim, "tests/test_orbital_period.toml")); - OrbitTracker* tracker = create_orbit_tracker(1); - - double max_time = MAX_SIMULATION_DAYS * SECONDS_PER_DAY; - while (sim->time < max_time && !tracker->orbit_completed) { - update_simulation(sim); - update_orbit_tracker(tracker, &sim->bodies[1], &sim->bodies[0], sim->time); - } + SECTION("Earth completes one orbit in ~365 days") { + OrbitTracker* tracker = create_orbit_tracker(1); + CelestialBody* earth = &sim->bodies[1]; + CelestialBody* sun = &sim->bodies[0]; - REQUIRE(tracker->orbit_completed); + const double MAX_DAYS = 400.0; + const double max_time = MAX_DAYS * SECONDS_PER_DAY; - double measured_period_days = tracker->time_at_completion / SECONDS_PER_DAY; - double period_error_days = fabs(measured_period_days - EXPECTED_PERIOD_DAYS); + while (sim->time < max_time && !tracker->orbit_completed) { + update_simulation(sim); + update_orbit_tracker(tracker, earth, sun, sim->time); + } - INFO("Expected period: " << EXPECTED_PERIOD_DAYS << " days"); - INFO("Measured period: " << measured_period_days << " days"); - INFO("Error: " << period_error_days << " days"); + REQUIRE(tracker->orbit_completed); - REQUIRE(period_error_days < 5.0); - - destroy_orbit_tracker(tracker); - destroy_simulation(sim); -} - -TEST_CASE("Orbital period - Mars (RK4)", "[period][rk4]") { - const double TIME_STEP = 60.0; - const double EXPECTED_PERIOD_DAYS = 687.0; - const double SECONDS_PER_DAY = 86400.0; - const double MAX_SIMULATION_DAYS = 750.0; + double measured_days = tracker->time_at_completion / SECONDS_PER_DAY; + INFO("Measured period: " << measured_days << " days"); - SimulationState* sim = create_simulation(10, 0, 0, TIME_STEP); + REQUIRE_THAT(measured_days, WithinAbs(365.2105, 0.1)); - REQUIRE(load_system_config(sim, "tests/test_orbital_period.toml")); + destroy_orbit_tracker(tracker); + } - OrbitTracker* tracker = create_orbit_tracker(2); + SECTION("Mars completes one orbit in ~671 days") { + OrbitTracker* tracker = create_orbit_tracker(2); + CelestialBody* mars = &sim->bodies[2]; + CelestialBody* sun = &sim->bodies[0]; - double max_time = MAX_SIMULATION_DAYS * SECONDS_PER_DAY; - while (sim->time < max_time && !tracker->orbit_completed) { - update_simulation(sim); - update_orbit_tracker(tracker, &sim->bodies[2], &sim->bodies[0], sim->time); - } + const double MAX_DAYS = 750.0; + const double max_time = MAX_DAYS * SECONDS_PER_DAY; - REQUIRE(tracker->orbit_completed); + while (sim->time < max_time && !tracker->orbit_completed) { + update_simulation(sim); + update_orbit_tracker(tracker, mars, sun, sim->time); + } - double measured_period_days = tracker->time_at_completion / SECONDS_PER_DAY; - double period_error_days = fabs(measured_period_days - EXPECTED_PERIOD_DAYS); + REQUIRE(tracker->orbit_completed); - INFO("Expected period: " << EXPECTED_PERIOD_DAYS << " days"); - INFO("Measured period: " << measured_period_days << " days"); - INFO("Error: " << period_error_days << " days"); + double measured_days = tracker->time_at_completion / SECONDS_PER_DAY; + INFO("Measured period: " << measured_days << " days"); - REQUIRE(period_error_days < 25.0); + REQUIRE_THAT(measured_days, WithinAbs(670.9345, 0.1)); - destroy_orbit_tracker(tracker); - destroy_simulation(sim); + destroy_orbit_tracker(tracker); + } } -TEST_CASE("Orbit direction - prograde for zero inclination", "[direction]") { +SCENARIO("Orbit direction for zero inclination", + "[direction][sanity]") { const double TIME_STEP = 60.0; const double TEST_DURATION_DAYS = 1.0; - const double SECONDS_PER_DAY = 86400.0; - const int STEPS = (int)(TEST_DURATION_DAYS * SECONDS_PER_DAY / TIME_STEP); + const int STEPS = (int)(TEST_DURATION_DAYS * 86400.0 / TIME_STEP); SimulationState* sim = create_simulation(2, 0, 0, TIME_STEP); REQUIRE(load_system_config(sim, "tests/test_energy.toml")); @@ -83,20 +75,21 @@ TEST_CASE("Orbit direction - prograde for zero inclination", "[direction]") { CelestialBody* sun = &sim->bodies[0]; CelestialBody* earth = &sim->bodies[1]; - Vec3 initial_rel_pos = vec3_sub(earth->global_position, sun->global_position); - double theta_start = atan2(initial_rel_pos.y, initial_rel_pos.x); + Vec3 initial_rel = vec3_sub(earth->global_position, sun->global_position); + double theta_start = atan2(initial_rel.y, initial_rel.x); for (int i = 0; i < STEPS; i++) { update_simulation(sim); } - Vec3 final_rel_pos = vec3_sub(earth->global_position, sun->global_position); - double theta_final = atan2(final_rel_pos.y, final_rel_pos.x); + Vec3 final_rel = vec3_sub(earth->global_position, sun->global_position); + double theta_final = atan2(final_rel.y, final_rel.x); + double delta = theta_final - theta_start; INFO("Initial angle: " << theta_start << " rad"); INFO("Final angle: " << theta_final << " rad"); + INFO("Delta: " << delta << " rad"); - REQUIRE(theta_final > theta_start); - - destroy_simulation(sim); + REQUIRE_THAT(delta, WithinAbs(0.0172042841, 1e-6)); + REQUIRE(delta > 0.0); } diff --git a/tests/test_orbital_period.toml b/tests/test_orbital_period.toml index f8fa430..e34c9f5 100644 --- a/tests/test_orbital_period.toml +++ b/tests/test_orbital_period.toml @@ -8,11 +8,7 @@ mass = 1.989e30 radius = 6.96e8 parent_index = -1 color = { r = 1.0, g = 1.0, b = 0.0 } -orbit = { - semi_major_axis = 0.0, - eccentricity = 0.0, - true_anomaly = 0.0 -} +orbit = { semi_major_axis = 0.0, eccentricity = 0.0, true_anomaly = 0.0 } [[bodies]] name = "Earth" @@ -20,11 +16,7 @@ mass = 5.972e24 radius = 6.371e6 parent_index = 0 color = { r = 0.0, g = 0.5, b = 1.0 } -orbit = { - semi_major_axis = 1.496e11, - eccentricity = 0.0, - true_anomaly = 0.0 -} +orbit = { semi_major_axis = 1.496e11, eccentricity = 0.0, true_anomaly = 0.0 } [[bodies]] name = "Mars" @@ -32,8 +24,4 @@ mass = 6.39e23 radius = 3.3895e6 parent_index = 0 color = { r = 0.8, g = 0.3, b = 0.1 } -orbit = { - semi_major_axis = 2.244e11, - eccentricity = 0.0, - true_anomaly = 0.0 -} +orbit = { semi_major_axis = 2.244e11, eccentricity = 0.0, true_anomaly = 0.0 }