Skip to content

Commit fad768c

Browse files
TST: Improve tests for Flight Data Exporter
1 parent 1ec0f48 commit fad768c

File tree

7 files changed

+170
-142
lines changed

7 files changed

+170
-142
lines changed

rocketpy/simulation/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .flight import Flight
2+
from .flight_data_exporter import FlightDataExporter
23
from .flight_data_importer import FlightDataImporter
34
from .monte_carlo import MonteCarlo
45
from .multivariate_rejection_sampler import MultivariateRejectionSampler
5-
from .flight_data_exporter import FlightDataExporter

rocketpy/simulation/flight.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
from ..prints.flight_prints import _FlightPrints
1616
from ..tools import (
1717
calculate_cubic_hermite_coefficients,
18+
deprecated,
1819
euler313_to_quaternions,
1920
find_closest,
2021
find_root_linear_interpolation,
2122
find_roots_cubic_function,
2223
quaternions_to_nutation,
2324
quaternions_to_precession,
2425
quaternions_to_spin,
25-
deprecated,
2626
)
2727

2828
ODE_SOLVER_MAP = {

rocketpy/simulation/flight_data_exporter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import json
6+
67
import numpy as np
78
import simplekml
89

tests/integration/test_encoding.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import pytest
66

77
from rocketpy._encoders import RocketPyDecoder, RocketPyEncoder
8-
98
from rocketpy.tools import from_hex_decode
109

1110

tests/integration/test_flight.py

Lines changed: 0 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -69,131 +69,6 @@ def test_all_info_different_solvers(
6969
assert test_flight.all_info() is None
7070

7171

72-
class TestExportData:
73-
"""Tests the export_data method of the Flight class."""
74-
75-
def test_basic_export(self, flight_calisto):
76-
"""Tests basic export functionality"""
77-
file_name = "test_export_data_1.csv"
78-
flight_calisto.export_data(file_name)
79-
self.validate_basic_export(flight_calisto, file_name)
80-
os.remove(file_name)
81-
82-
def test_custom_export(self, flight_calisto):
83-
"""Tests custom export functionality"""
84-
file_name = "test_export_data_2.csv"
85-
flight_calisto.export_data(
86-
file_name,
87-
"z",
88-
"vz",
89-
"e1",
90-
"w3",
91-
"angle_of_attack",
92-
time_step=0.1,
93-
)
94-
self.validate_custom_export(flight_calisto, file_name)
95-
os.remove(file_name)
96-
97-
def validate_basic_export(self, flight_calisto, file_name):
98-
"""Validates the basic export file content"""
99-
test_data = np.loadtxt(file_name, delimiter=",")
100-
assert np.allclose(flight_calisto.x[:, 0], test_data[:, 0], atol=1e-5)
101-
assert np.allclose(flight_calisto.x[:, 1], test_data[:, 1], atol=1e-5)
102-
assert np.allclose(flight_calisto.y[:, 1], test_data[:, 2], atol=1e-5)
103-
assert np.allclose(flight_calisto.z[:, 1], test_data[:, 3], atol=1e-5)
104-
assert np.allclose(flight_calisto.vx[:, 1], test_data[:, 4], atol=1e-5)
105-
assert np.allclose(flight_calisto.vy[:, 1], test_data[:, 5], atol=1e-5)
106-
assert np.allclose(flight_calisto.vz[:, 1], test_data[:, 6], atol=1e-5)
107-
assert np.allclose(flight_calisto.e0[:, 1], test_data[:, 7], atol=1e-5)
108-
assert np.allclose(flight_calisto.e1[:, 1], test_data[:, 8], atol=1e-5)
109-
assert np.allclose(flight_calisto.e2[:, 1], test_data[:, 9], atol=1e-5)
110-
assert np.allclose(flight_calisto.e3[:, 1], test_data[:, 10], atol=1e-5)
111-
assert np.allclose(flight_calisto.w1[:, 1], test_data[:, 11], atol=1e-5)
112-
assert np.allclose(flight_calisto.w2[:, 1], test_data[:, 12], atol=1e-5)
113-
assert np.allclose(flight_calisto.w3[:, 1], test_data[:, 13], atol=1e-5)
114-
115-
def validate_custom_export(self, flight_calisto, file_name):
116-
"""Validates the custom export file content"""
117-
test_data = np.loadtxt(file_name, delimiter=",")
118-
time_points = np.arange(flight_calisto.t_initial, flight_calisto.t_final, 0.1)
119-
assert np.allclose(time_points, test_data[:, 0], atol=1e-5)
120-
assert np.allclose(flight_calisto.z(time_points), test_data[:, 1], atol=1e-5)
121-
assert np.allclose(flight_calisto.vz(time_points), test_data[:, 2], atol=1e-5)
122-
assert np.allclose(flight_calisto.e1(time_points), test_data[:, 3], atol=1e-5)
123-
assert np.allclose(flight_calisto.w3(time_points), test_data[:, 4], atol=1e-5)
124-
assert np.allclose(
125-
flight_calisto.angle_of_attack(time_points), test_data[:, 5], atol=1e-5
126-
)
127-
128-
129-
def test_export_kml(flight_calisto_robust):
130-
"""Tests weather the method Flight.export_kml is working as intended.
131-
132-
Parameters:
133-
-----------
134-
flight_calisto_robust : rocketpy.Flight
135-
Flight object to be tested. See the conftest.py file for more info
136-
regarding this pytest fixture.
137-
"""
138-
139-
test_flight = flight_calisto_robust
140-
141-
# Basic export
142-
test_flight.export_kml(
143-
"test_export_data_1.kml", time_step=None, extrude=True, altitude_mode="absolute"
144-
)
145-
146-
# Load exported files and fixtures and compare them
147-
with open("test_export_data_1.kml", "r") as test_1:
148-
for row in test_1:
149-
if row[:29] == " <coordinates>":
150-
r = row[29:-15]
151-
r = r.split(",")
152-
for i, j in enumerate(r):
153-
r[i] = j.split(" ")
154-
lon, lat, z, coords = [], [], [], []
155-
for i in r:
156-
for j in i:
157-
coords.append(j)
158-
for i in range(0, len(coords), 3):
159-
lon.append(float(coords[i]))
160-
lat.append(float(coords[i + 1]))
161-
z.append(float(coords[i + 2]))
162-
os.remove("test_export_data_1.kml")
163-
164-
assert np.allclose(test_flight.latitude[:, 1], lat, atol=1e-3)
165-
assert np.allclose(test_flight.longitude[:, 1], lon, atol=1e-3)
166-
assert np.allclose(test_flight.z[:, 1], z, atol=1e-3)
167-
168-
169-
def test_export_pressures(flight_calisto_robust):
170-
"""Tests if the method Flight.export_pressures is working as intended.
171-
172-
Parameters
173-
----------
174-
flight_calisto_robust : Flight
175-
Flight object to be tested. See the conftest.py file for more info
176-
regarding this pytest fixture.
177-
"""
178-
file_name = "pressures.csv"
179-
time_step = 0.5
180-
parachute = flight_calisto_robust.rocket.parachutes[0]
181-
182-
flight_calisto_robust.export_pressures(file_name, time_step)
183-
184-
with open(file_name, "r") as file:
185-
contents = file.read()
186-
187-
expected_data = ""
188-
for t in np.arange(0, flight_calisto_robust.t_final, time_step):
189-
p_cl = parachute.clean_pressure_signal_function(t)
190-
p_ns = parachute.noisy_pressure_signal_function(t)
191-
expected_data += f"{t:f}, {p_cl:.5f}, {p_ns:.5f}\n"
192-
193-
assert contents == expected_data
194-
os.remove(file_name)
195-
196-
19772
@patch("matplotlib.pyplot.show")
19873
def test_hybrid_motor_flight(mock_show, flight_calisto_hybrid_modded): # pylint: disable=unused-argument
19974
"""Test the flight of a rocket with a hybrid motor. This test only validates
Lines changed: 164 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,53 @@
1+
"""Unit tests for FlightDataExporter class.
2+
3+
This module tests the data export functionality of the FlightDataExporter class,
4+
which exports flight simulation data to various formats (CSV, JSON, KML).
5+
"""
6+
17
import json
8+
9+
import numpy as np
10+
211
from rocketpy.simulation import FlightDataExporter
312

413

5-
def test_export_data_writes_csv_header(flight_calisto, tmp_path):
6-
"""Expect: direct exporter writes a CSV with a header containing 'Time (s)'."""
7-
out = tmp_path / "out.csv"
8-
FlightDataExporter(flight_calisto).export_data(str(out))
9-
text = out.read_text(encoding="utf-8")
10-
assert "Time (s)" in text
14+
def test_export_pressures_writes_csv_rows(flight_calisto_robust, tmp_path):
15+
"""Test that export_pressures writes CSV rows with pressure data.
1116
17+
Validates that the exported file contains multiple data rows in CSV format
18+
with 2-3 columns (time and pressure values).
1219
13-
def test_export_pressures_writes_rows(flight_calisto_robust, tmp_path):
14-
"""Expect: direct exporter writes a pressure file with time-first CSV rows."""
15-
out = tmp_path / "p.csv"
20+
Parameters
21+
----------
22+
flight_calisto_robust : rocketpy.Flight
23+
Flight object with parachutes configured.
24+
tmp_path : pathlib.Path
25+
Pytest fixture for temporary directories.
26+
"""
27+
out = tmp_path / "pressures.csv"
1628
FlightDataExporter(flight_calisto_robust).export_pressures(str(out), time_step=0.2)
1729
lines = out.read_text(encoding="utf-8").strip().splitlines()
1830
assert len(lines) > 5
19-
# basic CSV shape t, value
31+
# Basic CSV shape "t, value" or "t, clean, noisy"
2032
parts = lines[0].split(",")
2133
assert len(parts) in (2, 3)
2234

2335

24-
def test_export_sensor_data_writes_json_when_sensor_data_present(
25-
flight_calisto, tmp_path, monkeypatch
26-
):
27-
"""Expect: exporter writes JSON mapping sensor.name -> data when sensor_data is present."""
36+
def test_export_sensor_data_writes_json(flight_calisto, tmp_path, monkeypatch):
37+
"""Test that export_sensor_data writes JSON with sensor data.
38+
39+
Validates that sensor data is exported as JSON with sensor names as keys
40+
and measurement arrays as values.
41+
42+
Parameters
43+
----------
44+
flight_calisto : rocketpy.Flight
45+
Flight object to be tested.
46+
tmp_path : pathlib.Path
47+
Pytest fixture for temporary directories.
48+
monkeypatch : pytest.MonkeyPatch
49+
Pytest fixture for modifying attributes.
50+
"""
2851

2952
class DummySensor:
3053
def __init__(self, name):
@@ -40,3 +63,130 @@ def __init__(self, name):
4063

4164
data = json.loads(out.read_text(encoding="utf-8"))
4265
assert data["DummySensor"] == [1.0, 2.0, 3.0]
66+
67+
68+
def test_export_data_default_variables(flight_calisto, tmp_path):
69+
"""Test export_data with default variables (full solution matrix).
70+
71+
Validates that all state variables are exported correctly when no specific
72+
variables are requested: position (x, y, z), velocity (vx, vy, vz),
73+
quaternions (e0, e1, e2, e3), and angular velocities (w1, w2, w3).
74+
75+
Parameters
76+
----------
77+
flight_calisto : rocketpy.Flight
78+
Flight object to be tested.
79+
tmp_path : pathlib.Path
80+
Pytest fixture for temporary directories.
81+
"""
82+
file_name = tmp_path / "flight_data.csv"
83+
FlightDataExporter(flight_calisto).export_data(str(file_name))
84+
85+
test_data = np.loadtxt(file_name, delimiter=",", skiprows=1)
86+
87+
# Verify time column
88+
assert np.allclose(flight_calisto.x[:, 0], test_data[:, 0], atol=1e-5)
89+
90+
# Verify position
91+
assert np.allclose(flight_calisto.x[:, 1], test_data[:, 1], atol=1e-5)
92+
assert np.allclose(flight_calisto.y[:, 1], test_data[:, 2], atol=1e-5)
93+
assert np.allclose(flight_calisto.z[:, 1], test_data[:, 3], atol=1e-5)
94+
95+
# Verify velocity
96+
assert np.allclose(flight_calisto.vx[:, 1], test_data[:, 4], atol=1e-5)
97+
assert np.allclose(flight_calisto.vy[:, 1], test_data[:, 5], atol=1e-5)
98+
assert np.allclose(flight_calisto.vz[:, 1], test_data[:, 6], atol=1e-5)
99+
100+
# Verify quaternions
101+
assert np.allclose(flight_calisto.e0[:, 1], test_data[:, 7], atol=1e-5)
102+
assert np.allclose(flight_calisto.e1[:, 1], test_data[:, 8], atol=1e-5)
103+
assert np.allclose(flight_calisto.e2[:, 1], test_data[:, 9], atol=1e-5)
104+
assert np.allclose(flight_calisto.e3[:, 1], test_data[:, 10], atol=1e-5)
105+
106+
# Verify angular velocities
107+
assert np.allclose(flight_calisto.w1[:, 1], test_data[:, 11], atol=1e-5)
108+
assert np.allclose(flight_calisto.w2[:, 1], test_data[:, 12], atol=1e-5)
109+
assert np.allclose(flight_calisto.w3[:, 1], test_data[:, 13], atol=1e-5)
110+
111+
112+
def test_export_data_custom_variables_and_time_step(flight_calisto, tmp_path):
113+
"""Test export_data with custom variables and time step.
114+
115+
Validates that specific variables can be exported with custom time intervals,
116+
including derived quantities like angle of attack.
117+
118+
Parameters
119+
----------
120+
flight_calisto : rocketpy.Flight
121+
Flight object to be tested.
122+
tmp_path : pathlib.Path
123+
Pytest fixture for temporary directories.
124+
"""
125+
file_name = tmp_path / "custom_flight_data.csv"
126+
time_step = 0.1
127+
128+
FlightDataExporter(flight_calisto).export_data(
129+
str(file_name),
130+
"z",
131+
"vz",
132+
"e1",
133+
"w3",
134+
"angle_of_attack",
135+
time_step=time_step,
136+
)
137+
138+
test_data = np.loadtxt(file_name, delimiter=",", skiprows=1)
139+
time_points = np.arange(flight_calisto.t_initial, flight_calisto.t_final, time_step)
140+
141+
# Verify time column
142+
assert np.allclose(time_points, test_data[:, 0], atol=1e-5)
143+
144+
# Verify custom variables
145+
assert np.allclose(flight_calisto.z(time_points), test_data[:, 1], atol=1e-5)
146+
assert np.allclose(flight_calisto.vz(time_points), test_data[:, 2], atol=1e-5)
147+
assert np.allclose(flight_calisto.e1(time_points), test_data[:, 3], atol=1e-5)
148+
assert np.allclose(flight_calisto.w3(time_points), test_data[:, 4], atol=1e-5)
149+
assert np.allclose(
150+
flight_calisto.angle_of_attack(time_points), test_data[:, 5], atol=1e-5
151+
)
152+
153+
154+
def test_export_kml_trajectory(flight_calisto_robust, tmp_path):
155+
"""Test export_kml creates valid KML file with trajectory coordinates.
156+
157+
Validates that the KML export contains correct latitude, longitude, and
158+
altitude coordinates for the flight trajectory in absolute altitude mode.
159+
160+
Parameters
161+
----------
162+
flight_calisto_robust : rocketpy.Flight
163+
Flight object to be tested.
164+
tmp_path : pathlib.Path
165+
Pytest fixture for temporary directories.
166+
"""
167+
file_name = tmp_path / "trajectory.kml"
168+
FlightDataExporter(flight_calisto_robust).export_kml(
169+
str(file_name), time_step=None, extrude=True, altitude_mode="absolute"
170+
)
171+
172+
# Parse KML coordinates
173+
with open(file_name, "r") as kml_file:
174+
for row in kml_file:
175+
if row.strip().startswith("<coordinates>"):
176+
coords_str = (
177+
row.strip()
178+
.replace("<coordinates>", "")
179+
.replace("</coordinates>", "")
180+
)
181+
coords_list = coords_str.strip().split(" ")
182+
183+
# Extract lon, lat, z from coordinates
184+
parsed_coords = [c.split(",") for c in coords_list]
185+
lon = [float(point[0]) for point in parsed_coords]
186+
lat = [float(point[1]) for point in parsed_coords]
187+
z = [float(point[2]) for point in parsed_coords]
188+
189+
# Verify coordinates match flight data
190+
assert np.allclose(flight_calisto_robust.latitude[:, 1], lat, atol=1e-3)
191+
assert np.allclose(flight_calisto_robust.longitude[:, 1], lon, atol=1e-3)
192+
assert np.allclose(flight_calisto_robust.z[:, 1], z, atol=1e-3)

tests/unit/test_flight_export_deprecation.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from unittest.mock import patch
2+
23
import pytest
34

5+
# TODO: these tests should be deleted after the deprecated methods are removed
6+
47

58
def test_export_data_deprecated_emits_warning_and_delegates(flight_calisto, tmp_path):
69
"""Expect: calling Flight.export_data emits DeprecationWarning and delegates to FlightDataExporter.export_data."""

0 commit comments

Comments
 (0)