STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_csv_to_eng.py
Go to the documentation of this file.
1"""
2Tests for csv_to_eng module.
3"""
4
5import tempfile
6from pathlib import Path
7
8import pytest
9
10from csv_to_eng import (
11 EngineMetadata,
12 read_csv_curve,
13 normalize_curve,
14 write_eng,
15 convert,
16 LBF_TO_N,
17)
18
19
21 """Tests for EngineMetadata dataclass."""
22
24 meta = EngineMetadata()
25 assert meta.name == "Unknown"
26 assert meta.diameter_mm == 0.0
27 assert meta.manufacturer == "Unknown"
28
30 meta = EngineMetadata(
31 name="K1000",
32 diameter_mm=54,
33 length_mm=326,
34 delays="6-10-14",
35 prop_mass_kg=0.85,
36 total_mass_kg=1.45,
37 manufacturer="Acme Rocketry",
38 )
39 header = meta.header_line()
40 assert header == "K1000 54 326 6-10-14 0.8500 1.4500 Acme_Rocketry"
41
43 meta = EngineMetadata(name="My Motor", manufacturer="Space Co")
44 header = meta.header_line()
45 assert "My_Motor" in header
46 assert "Space_Co" in header
47
48
50 """Tests for read_csv_curve function."""
51
52 def test_basic_csv(self):
53 csv_content = """time,thrust
540.0,0.0
550.1,100.5
560.2,150.2
570.3,0.0
58"""
59 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
60 f.write(csv_content)
61 csv_path = f.name
62
63 try:
64 metadata, points = read_csv_curve(csv_path)
65 assert len(points) == 4
66 assert points[0] == (0.0, 0.0)
67 assert points[1] == (0.1, 100.5)
68 assert points[3] == (0.3, 0.0)
69 finally:
70 Path(csv_path).unlink()
71
73 csv_content = """t,F
740.0,50.0
750.5,200.0
76"""
77 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
78 f.write(csv_content)
79 csv_path = f.name
80
81 try:
82 metadata, points = read_csv_curve(csv_path)
83 assert len(points) == 2
84 assert points[1] == (0.5, 200.0)
85 finally:
86 Path(csv_path).unlink()
87
89 csv_content = """# This is a comment
90time,thrust
91// Another comment
920.0,0.0
930.1,100.0
94"""
95 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
96 f.write(csv_content)
97 csv_path = f.name
98
99 try:
100 metadata, points = read_csv_curve(csv_path)
101 assert len(points) == 2
102 finally:
103 Path(csv_path).unlink()
104
106 csv_content = """time,thrust,temperature,pressure
1070.0,0.0,25.0,101.3
1080.1,100.0,30.0,102.0
109"""
110 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
111 f.write(csv_content)
112 csv_path = f.name
113
114 try:
115 metadata, points = read_csv_curve(csv_path)
116 assert len(points) == 2
117 assert points[0] == (0.0, 0.0)
118 assert points[1] == (0.1, 100.0)
119 finally:
120 Path(csv_path).unlink()
121
123 csv_content = """time,thrust,engine_name,diameter_mm,length_mm,manufacturer
1240.0,0.0,K500,38,200,TestCo
1250.1,500.0,,,,,
126"""
127 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
128 f.write(csv_content)
129 csv_path = f.name
130
131 try:
132 metadata, points = read_csv_curve(csv_path)
133 assert metadata.name == "K500"
134 assert metadata.diameter_mm == 38.0
135 assert metadata.length_mm == 200.0
136 assert metadata.manufacturer == "TestCo"
137 finally:
138 Path(csv_path).unlink()
139
141 csv_content = """time_ms,thrust
1420,0.0
143100,100.0
144200,0.0
145"""
146 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
147 f.write(csv_content)
148 csv_path = f.name
149
150 try:
151 metadata, points = read_csv_curve(csv_path)
152 assert points[0] == (0.0, 0.0)
153 assert points[1] == (0.1, 100.0) # 100ms = 0.1s
154 assert points[2] == (0.2, 0.0)
155 finally:
156 Path(csv_path).unlink()
157
159 csv_content = """time,thrust_lbf
1600.0,0.0
1610.1,10.0
162"""
163 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
164 f.write(csv_content)
165 csv_path = f.name
166
167 try:
168 metadata, points = read_csv_curve(csv_path)
169 expected_thrust_n = 10.0 * LBF_TO_N
170 assert abs(points[1][1] - expected_thrust_n) < 0.01
171 finally:
172 Path(csv_path).unlink()
173
175 csv_content = """time,thrust
1760,0.0
177100,22.5
178"""
179 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
180 f.write(csv_content)
181 csv_path = f.name
182
183 try:
184 # Force ms and lbf interpretation
185 metadata, points = read_csv_curve(csv_path, time_units="ms", thrust_units="lbf")
186 assert points[1][0] == 0.1 # 100ms -> 0.1s
187 expected_thrust = 22.5 * LBF_TO_N
188 assert abs(points[1][1] - expected_thrust) < 0.01
189 finally:
190 Path(csv_path).unlink()
191
193 csv_content = """time,thrust
1940.0,-5.0
1950.1,100.0
196"""
197 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
198 f.write(csv_content)
199 csv_path = f.name
200
201 try:
202 metadata, points = read_csv_curve(csv_path)
203 assert points[0][1] == 0.0 # Negative clamped to 0
204 finally:
205 Path(csv_path).unlink()
206
208 csv_content = """time,thrust
209-0.1,0.0
2100.0,100.0
211"""
212 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
213 f.write(csv_content)
214 csv_path = f.name
215
216 try:
217 with pytest.raises(ValueError, match="Negative time"):
218 read_csv_curve(csv_path)
219 finally:
220 Path(csv_path).unlink()
221
223 # First two columns are numeric - no header detected
224 csv_content = """0.0,0.0
2250.1,100.0
2260.2,50.0
227"""
228 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
229 f.write(csv_content)
230 csv_path = f.name
231
232 try:
233 metadata, points = read_csv_curve(csv_path)
234 assert len(points) == 3
235 assert points[1] == (0.1, 100.0)
236 finally:
237 Path(csv_path).unlink()
238
240 csv_content = """ time , thrust
241 0.0 , 0.0
242 0.1 , 100.0
243"""
244 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
245 f.write(csv_content)
246 csv_path = f.name
247
248 try:
249 metadata, points = read_csv_curve(csv_path)
250 assert len(points) == 2
251 finally:
252 Path(csv_path).unlink()
253
254
256 """Tests for normalize_curve function."""
257
259 """Curve already starts at 0 and ends at 0."""
260 points = [(0.0, 0.0), (0.1, 100.0), (0.2, 0.0)]
261 normalized = normalize_curve(points)
262 assert normalized == [(0.0, 0.0), (0.1, 100.0), (0.2, 0.0)]
263
265 """Insert t=0 if curve doesn't start there."""
266 points = [(0.05, 100.0), (0.1, 150.0), (0.2, 0.0)]
267 normalized = normalize_curve(points)
268 assert normalized[0] == (0.0, 0.0)
269 assert normalized[1] == (0.05, 100.0)
270
272 """Append thrust=0 at end if curve doesn't end at 0."""
273 points = [(0.0, 0.0), (0.1, 100.0), (0.2, 50.0)]
274 normalized = normalize_curve(points)
275 assert normalized[-1][1] == 0.0
276 assert normalized[-1][0] > 0.2 # Slightly after last point
277
278 def test_sorting(self):
279 """Unsorted points should be sorted by time."""
280 points = [(0.2, 50.0), (0.0, 0.0), (0.1, 100.0), (0.15, 120.0)]
281 normalized = normalize_curve(points)
282 times = [p[0] for p in normalized]
283 assert times == sorted(times)
284
286 """Duplicate times should keep the last value."""
287 points = [(0.0, 0.0), (0.1, 100.0), (0.1, 150.0), (0.2, 0.0)]
288 normalized = normalize_curve(points)
289 # Should have t=0.1 with value 150.0 (last one)
290 t_01_point = [p for p in normalized if p[0] == 0.1][0]
291 assert t_01_point[1] == 150.0
292
293 def test_rounding(self):
294 """Values should be rounded appropriately."""
295 points = [(0.0, 0.0), (0.12345, 100.5678)]
296 normalized = normalize_curve(points, time_decimals=3, thrust_decimals=1)
297
298 # Find the point at ~0.123
299 mid_point = [p for p in normalized if 0.12 < p[0] < 0.13][0]
300 assert mid_point[0] == 0.123
301 assert mid_point[1] == 100.6
302
304 """Empty input should return minimal valid curve."""
305 normalized = normalize_curve([])
306 assert normalized == [(0.0, 0.0)]
307
308
310 """Tests for write_eng function."""
311
313 metadata = EngineMetadata(
314 name="TestMotor",
315 diameter_mm=29,
316 length_mm=124,
317 delays="5",
318 prop_mass_kg=0.05,
319 total_mass_kg=0.1,
320 manufacturer="TestMfr",
321 )
322 points = [(0.0, 0.0), (0.1, 100.0), (0.2, 0.0)]
323
324 with tempfile.NamedTemporaryFile(mode="w", suffix=".eng", delete=False) as f:
325 eng_path = f.name
326
327 try:
328 write_eng(eng_path, metadata, points)
329
330 content = Path(eng_path).read_text()
331 lines = content.strip().split("\n")
332
333 # Check comment line
334 assert lines[0].startswith(";")
335
336 # Check header
337 assert "TestMotor 29 124 5 0.0500 0.1000 TestMfr" in lines[1]
338
339 # Check data lines
340 assert "0.000 0.0" in lines[2]
341 assert "0.100 100.0" in lines[3]
342 assert "0.200 0.0" in lines[4]
343 finally:
344 Path(eng_path).unlink()
345
346
348 """Tests for the high-level convert function."""
349
351 csv_content = """time,thrust
3520.0,0.0
3530.1,100.0
3540.2,150.0
3550.3,50.0
356"""
357 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
358 f.write(csv_content)
359 csv_path = f.name
360
361 eng_path = csv_path.replace(".csv", ".eng")
362
363 try:
364 convert(
365 csv_path,
366 eng_path,
367 name="TestEngine",
368 diameter_mm=38,
369 length_mm=200,
370 manufacturer="PyTest",
371 )
372
373 content = Path(eng_path).read_text()
374 assert "TestEngine" in content
375 assert "38" in content
376 assert "PyTest" in content
377
378 # Should have appended thrust=0 at end
379 lines = content.strip().split("\n")
380 last_line = lines[-1]
381 assert last_line.endswith(" 0.0")
382
383 finally:
384 Path(csv_path).unlink()
385 if Path(eng_path).exists():
386 Path(eng_path).unlink()
387
389 # CSV with values in ms and lbf
390 csv_content = """time,thrust
3910,0.0
392100,10.0
393200,5.0
394"""
395 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
396 f.write(csv_content)
397 csv_path = f.name
398
399 eng_path = csv_path.replace(".csv", ".eng")
400
401 try:
402 convert(
403 csv_path,
404 eng_path,
405 time_units="ms",
406 thrust_units="lbf",
407 )
408
409 content = Path(eng_path).read_text()
410 lines = content.strip().split("\n")
411
412 # Check time conversion (100ms -> 0.100s)
413 assert "0.100" in lines[3]
414
415 # Check thrust conversion (10 lbf -> ~44.5 N)
416 data_line = lines[3]
417 thrust_val = float(data_line.split()[1])
418 expected = 10.0 * LBF_TO_N
419 assert abs(thrust_val - expected) < 0.1
420
421 finally:
422 Path(csv_path).unlink()
423 if Path(eng_path).exists():
424 Path(eng_path).unlink()
425
426
428 """Tests for CLI functionality."""
429
430 def test_cli_basic(self):
431 from csv_to_eng.cli import main
432
433 csv_content = """time,thrust
4340.0,0.0
4350.1,100.0
4360.2,0.0
437"""
438 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
439 f.write(csv_content)
440 csv_path = f.name
441
442 eng_path = csv_path.replace(".csv", ".eng")
443
444 try:
445 exit_code = main([
446 "--input", csv_path,
447 "--output", eng_path,
448 "--name", "CLITest",
449 ])
450
451 assert exit_code == 0
452 assert Path(eng_path).exists()
453 content = Path(eng_path).read_text()
454 assert "CLITest" in content
455
456 finally:
457 Path(csv_path).unlink()
458 if Path(eng_path).exists():
459 Path(eng_path).unlink()
460
462 from csv_to_eng.cli import main
463
464 exit_code = main([
465 "--input", "/nonexistent/file.csv",
466 "--output", "/tmp/output.eng",
467 ])
468
469 assert exit_code == 1 # Error exit code
static std::vector< string_t > split(const string_t &reference_string)
split the string input to reference tokens
Definition json.hpp:14471
json_pointer< string_t > convert() const &
Definition json.hpp:14640
int main()