Browse Source

refactor: rewrite test_orbital_period with SCENARIO/SECTION pattern

- 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
test-refactor
cinnaboot 2 months ago
parent
commit
0091d02704
  1. 34
      continue.md
  2. 12
      tests/test_energy.toml
  3. 103
      tests/test_orbital_period.cpp
  4. 18
      tests/test_orbital_period.toml

34
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.**

12
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 }

103
tests/test_orbital_period.cpp

@ -1,81 +1,73 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "../src/physics.h"
#include "../src/simulation.h"
#include "../src/config_loader.h"
#include "../src/test_utilities.h"
#include <cmath>
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);
}

18
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 }

Loading…
Cancel
Save