Skip to content

Commit 6b46322

Browse files
TST: increase test coverage (#756)
* TST: add flight fixture for solid propulsion equations of motion * DEV: add pragma comments to exclude specific lines from coverage * TST: adds more unit tests to the codebase MNT: linters TST: complementing tests for sensitivity analysis and removing duplicate piece of code. DEV: add pragma comments to exclude specific lines from coverage MNT: fix pylint error * TST: add fixture for solid propulsion equations of motion in flight tests * TST: fix tests not passing
1 parent 48efc87 commit 6b46322

27 files changed

+544
-119
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@
262262
"rtol",
263263
"rtype",
264264
"rucsoundings",
265+
"runslow",
265266
"rwork",
266267
"savetxt",
267268
"savgol",

rocketpy/environment/environment.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,7 @@ def __initialize_utm_coordinates(self):
459459
flattening=self.ellipsoid.flattening,
460460
semi_major_axis=self.ellipsoid.semi_major_axis,
461461
)
462-
else:
463-
# pragma: no cover
462+
else: # pragma: no cover
464463
warnings.warn(
465464
"UTM coordinates are not available for latitudes "
466465
"above 84 or below -80 degrees. The UTM conversions will fail."
@@ -715,8 +714,8 @@ def set_location(self, latitude, longitude):
715714

716715
if not isinstance(latitude, NUMERICAL_TYPES) and isinstance(
717716
longitude, NUMERICAL_TYPES
718-
):
719-
# pragma: no cover
717+
): # pragma: no cover
718+
720719
raise TypeError("Latitude and Longitude must be numbers!")
721720

722721
# Store latitude and longitude
@@ -812,8 +811,8 @@ def max_expected_height(self):
812811

813812
@max_expected_height.setter
814813
def max_expected_height(self, value):
815-
if value < self.elevation:
816-
raise ValueError( # pragma: no cover
814+
if value < self.elevation: # pragma: no cover
815+
raise ValueError(
817816
"Max expected height cannot be lower than the surface elevation"
818817
)
819818
self._max_expected_height = value
@@ -952,8 +951,8 @@ def get_elevation_from_topographic_profile(self, lat, lon):
952951
Elevation provided by the topographic data, in meters.
953952
"""
954953
# TODO: refactor this method. pylint: disable=too-many-statements
955-
if self.topographic_profile_activated is False:
956-
raise ValueError( # pragma: no cover
954+
if self.topographic_profile_activated is False: # pragma: no cover
955+
raise ValueError(
957956
"You must define a Topographic profile first, please use the "
958957
"Environment.set_topographic_profile() method first."
959958
)
@@ -1285,8 +1284,8 @@ def set_atmospheric_model( # pylint: disable=too-many-statements
12851284
self.process_forecast_reanalysis(dataset, dictionary)
12861285
else:
12871286
self.process_ensemble(dataset, dictionary)
1288-
else:
1289-
raise ValueError(f"Unknown model type '{type}'.") # pragma: no cover
1287+
else: # pragma: no cover
1288+
raise ValueError(f"Unknown model type '{type}'.")
12901289

12911290
if type not in ["ensemble"]:
12921291
# Ensemble already computed these values
@@ -2578,7 +2577,7 @@ def set_earth_geometry(self, datum):
25782577
}
25792578
try:
25802579
return ellipsoid[datum]
2581-
except KeyError as e:
2580+
except KeyError as e: # pragma: no cover
25822581
available_datums = ', '.join(ellipsoid.keys())
25832582
raise AttributeError(
25842583
f"The reference system '{datum}' is not recognized. Please use one of "
@@ -2845,7 +2844,7 @@ def from_dict(cls, data): # pylint: disable=too-many-statements
28452844
return env
28462845

28472846

2848-
if __name__ == "__main__":
2847+
if __name__ == "__main__": # pragma: no cover
28492848
import doctest
28502849

28512850
results = doctest.testmod()

rocketpy/environment/fetchers.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ def fetch_atmospheric_data_from_windy(lat, lon, model):
7979

8080
try:
8181
response = requests.get(url).json()
82-
if "data" not in response.keys():
82+
if "data" not in response.keys(): # pragma: no cover
8383
raise ValueError(
8484
f"Could not get a valid response for '{model}' from Windy. "
8585
"Check if the coordinates are set inside the model's domain."
8686
)
87-
except requests.exceptions.RequestException as e:
87+
except requests.exceptions.RequestException as e: # pragma: no cover
8888
if model == "iconEu":
8989
raise ValueError(
9090
"Could not get a valid response for Icon-EU from Windy. "
@@ -315,8 +315,8 @@ def fetch_wyoming_sounding(file):
315315
If the response indicates the output format is invalid.
316316
"""
317317
response = requests.get(file)
318-
if response.status_code != 200:
319-
raise ImportError(f"Unable to load {file}.") # pragma: no cover
318+
if response.status_code != 200: # pragma: no cover
319+
raise ImportError(f"Unable to load {file}.")
320320
if len(re.findall("Can't get .+ Observations at", response.text)):
321321
raise ValueError(
322322
re.findall("Can't get .+ Observations at .+", response.text)[0]
@@ -330,7 +330,7 @@ def fetch_wyoming_sounding(file):
330330

331331

332332
@exponential_backoff(max_attempts=5, base_delay=2, max_delay=60)
333-
def fetch_noaaruc_sounding(file):
333+
def fetch_noaaruc_sounding(file): # pragma: no cover
334334
"""Fetches sounding data from a specified file using the NOAA RUC soundings.
335335
336336
Parameters

rocketpy/environment/tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ def utm_to_geodesic( # pylint: disable=too-many-locals,too-many-statements
590590
return lat, lon
591591

592592

593-
if __name__ == "__main__":
593+
if __name__ == "__main__": # pragma: no cover
594594
import doctest
595595

596596
results = doctest.testmod()

rocketpy/mathutils/function.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,7 +1481,7 @@ def plot(self, *args, **kwargs):
14811481
else:
14821482
print("Error: Only functions with 1D or 2D domains can be plotted.")
14831483

1484-
def plot1D(self, *args, **kwargs):
1484+
def plot1D(self, *args, **kwargs): # pragma: no cover
14851485
"""Deprecated method, use Function.plot_1d instead."""
14861486
warnings.warn(
14871487
"The `Function.plot1D` method is set to be deprecated and fully "
@@ -1581,7 +1581,7 @@ def plot_1d( # pylint: disable=too-many-statements
15811581
if return_object:
15821582
return fig, ax
15831583

1584-
def plot2D(self, *args, **kwargs):
1584+
def plot2D(self, *args, **kwargs): # pragma: no cover
15851585
"""Deprecated method, use Function.plot_2d instead."""
15861586
warnings.warn(
15871587
"The `Function.plot2D` method is set to be deprecated and fully "
@@ -2772,7 +2772,7 @@ def differentiate_complex_step(self, x, dx=1e-200, order=1):
27722772
"""
27732773
if order == 1:
27742774
return float(self.get_value_opt(x + dx * 1j).imag / dx)
2775-
else:
2775+
else: # pragma: no cover
27762776
raise NotImplementedError(
27772777
"Only 1st order derivatives are supported yet. Set order=1."
27782778
)
@@ -3119,12 +3119,12 @@ def compose(self, func, extrapolate=False):
31193119
The result of inputting the function into the function.
31203120
"""
31213121
# Check if the input is a function
3122-
if not isinstance(func, Function):
3122+
if not isinstance(func, Function): # pragma: no cover
31233123
raise TypeError("Input must be a Function object.")
31243124

31253125
if isinstance(self.source, np.ndarray) and isinstance(func.source, np.ndarray):
31263126
# Perform bounds check for composition
3127-
if not extrapolate:
3127+
if not extrapolate: # pragma: no cover
31283128
if func.min < self.x_initial or func.max > self.x_final:
31293129
raise ValueError(
31303130
f"Input Function image {func.min, func.max} must be within "
@@ -3197,7 +3197,7 @@ def savetxt(
31973197

31983198
# create the datapoints
31993199
if callable(self.source):
3200-
if lower is None or upper is None or samples is None:
3200+
if lower is None or upper is None or samples is None: # pragma: no cover
32013201
raise ValueError(
32023202
"If the source is a callable, lower, upper and samples"
32033203
+ " must be provided."
@@ -3264,7 +3264,7 @@ def __validate_source(self, source): # pylint: disable=too-many-statements
32643264
self.__inputs__ = header[:-1]
32653265
if self.__outputs__ is None:
32663266
self.__outputs__ = [header[-1]]
3267-
except Exception as e:
3267+
except Exception as e: # pragma: no cover
32683268
raise ValueError(
32693269
"Could not read the csv or txt file to create Function source."
32703270
) from e
@@ -3323,6 +3323,7 @@ def __validate_inputs(self, inputs):
33233323
if isinstance(inputs, (list, tuple)):
33243324
if len(inputs) == 1:
33253325
return inputs
3326+
# pragma: no cover
33263327
raise ValueError(
33273328
"Inputs must be a string or a list of strings with "
33283329
"the length of the domain dimension."
@@ -3335,6 +3336,7 @@ def __validate_inputs(self, inputs):
33353336
isinstance(i, str) for i in inputs
33363337
):
33373338
return inputs
3339+
# pragma: no cover
33383340
raise ValueError(
33393341
"Inputs must be a list of strings with "
33403342
"the length of the domain dimension."
@@ -3611,7 +3613,7 @@ def reset_funcified_methods(instance):
36113613
instance.__dict__.pop(key)
36123614

36133615

3614-
if __name__ == "__main__":
3616+
if __name__ == "__main__": # pragma: no cover
36153617
import doctest
36163618

36173619
results = doctest.testmod()

rocketpy/mathutils/vector_matrix.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,7 @@ def from_dict(cls, data):
11031103
return cls(data)
11041104

11051105

1106-
if __name__ == "__main__":
1106+
if __name__ == "__main__": # pragma: no cover
11071107
import doctest
11081108

11091109
results = doctest.testmod()

rocketpy/motors/fluid.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ def __post_init__(self):
3030
If the density is not a positive number.
3131
"""
3232

33-
if not isinstance(self.name, str):
33+
if not isinstance(self.name, str): # pragma: no cover
3434
raise ValueError("The name must be a string.")
35-
if self.density < 0:
35+
if self.density < 0: # pragma: no cover
3636
raise ValueError("The density must be a positive number.")
3737

3838
# Initialize plots and prints object

rocketpy/motors/motor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ class Function. Thrust units are Newtons.
247247
self._csys = 1
248248
elif coordinate_system_orientation == "combustion_chamber_to_nozzle":
249249
self._csys = -1
250-
else:
250+
else: # pragma: no cover
251251
raise ValueError(
252252
"Invalid coordinate system orientation. Options are "
253253
"'nozzle_to_combustion_chamber' and 'combustion_chamber_to_nozzle'."
@@ -346,7 +346,7 @@ def burn_time(self, burn_time):
346346
else:
347347
if not callable(self.thrust.source):
348348
self._burn_time = (self.thrust.x_array[0], self.thrust.x_array[-1])
349-
else:
349+
else: # pragma: no cover
350350
raise ValueError(
351351
"When using a float or callable as thrust source, a burn_time"
352352
" argument must be specified."

rocketpy/plots/rocket_plots.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ def _draw_generic_surface(
385385
x_pos = position[2]
386386
# y position of the surface is the y position in the plot
387387
y_pos = position[1]
388-
else:
388+
else: # pragma: no cover
389389
raise ValueError("Plane must be 'xz' or 'yz'.")
390390

391391
ax.scatter(
@@ -633,7 +633,7 @@ def _draw_sensors(self, ax, sensors, plane):
633633
# y position of the sensor is the y position in the plot
634634
y_pos = pos[1]
635635
normal_y = sensor.normal_vector.y
636-
else:
636+
else: # pragma: no cover
637637
raise ValueError("Plane must be 'xz' or 'yz'.")
638638

639639
# line length is 2/5 of the rocket radius

rocketpy/prints/compare_prints.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
class _ComparePrints:
1+
class _ComparePrints: # pragma: no cover
22
def __init__(self) -> None:
33
pass

rocketpy/rocket/aero_surface/nose_cone.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def __init__( # pylint: disable=too-many-statements
135135
self._base_radius = base_radius
136136
self._length = length
137137
if bluffness is not None:
138-
if bluffness > 1 or bluffness < 0:
138+
if bluffness > 1 or bluffness < 0: # pragma: no cover
139139
raise ValueError(
140140
f"Bluffness ratio of {bluffness} is out of range. "
141141
"It must be between 0 and 1."
@@ -286,7 +286,7 @@ def theta(x):
286286
self.y_nosecone = Function(
287287
lambda x: self.base_radius * np.power(x / self.length, self.power)
288288
)
289-
else:
289+
else: # pragma: no cover
290290
raise ValueError(
291291
f"Nose Cone kind '{self.kind}' not found, "
292292
+ "please use one of the following Nose Cone kinds:"
@@ -317,12 +317,11 @@ def bluffness(self, value):
317317
raise ValueError(
318318
"Parameter 'bluffness' must be None or 0 when using a nose cone kind 'powerseries'."
319319
)
320-
if value is not None:
321-
if value > 1 or value < 0:
322-
raise ValueError(
323-
f"Bluffness ratio of {value} is out of range. "
324-
"It must be between 0 and 1."
325-
)
320+
if value is not None and not 0 <= value <= 1: # pragma: no cover
321+
raise ValueError(
322+
f"Bluffness ratio of {value} is out of range. "
323+
"It must be between 0 and 1."
324+
)
326325
self._bluffness = value
327326
self.evaluate_nose_shape()
328327

rocketpy/rocket/rocket.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ def __init__( # pylint: disable=too-many-statements
278278
self._csys = 1
279279
elif coordinate_system_orientation == "nose_to_tail":
280280
self._csys = -1
281-
else:
281+
else: # pragma: no cover
282282
raise TypeError(
283283
"Invalid coordinate system orientation. Please choose between "
284284
+ '"tail_to_nose" and "nose_to_tail".'
@@ -1173,7 +1173,7 @@ def add_nose(
11731173
self.add_surfaces(nose, position)
11741174
return nose
11751175

1176-
def add_fins(self, *args, **kwargs):
1176+
def add_fins(self, *args, **kwargs): # pragma: no cover
11771177
"""See Rocket.add_trapezoidal_fins for documentation.
11781178
This method is set to be deprecated in version 1.0.0 and fully removed
11791179
by version 2.0.0. Use Rocket.add_trapezoidal_fins instead. It keeps the

rocketpy/sensitivity/sensitivity_model.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,6 @@ def set_target_variables_nominal(self, target_variables_nominal_value):
140140
self.target_variables_info[target_variable]["nominal_value"] = (
141141
target_variables_nominal_value[i]
142142
)
143-
for i, target_variable in enumerate(self.target_variables_names):
144-
self.target_variables_info[target_variable]["nominal_value"] = (
145-
target_variables_nominal_value[i]
146-
)
147143

148144
self._nominal_target_passed = True
149145

@@ -356,12 +352,12 @@ def __check_requirements(self):
356352
version = ">=0" if not version else version
357353
try:
358354
check_requirement_version(module_name, version)
359-
except (ValueError, ImportError) as e:
355+
except (ValueError, ImportError) as e: # pragma: no cover
360356
has_error = True
361357
print(
362358
f"The following error occurred while importing {module_name}: {e}"
363359
)
364-
if has_error:
360+
if has_error: # pragma: no cover
365361
print(
366362
"Given the above errors, some methods may not work. Please run "
367363
+ "'pip install rocketpy[sensitivity]' to install extra requirements."

0 commit comments

Comments
 (0)