API
 
Loading...
Searching...
No Matches
adcTracker_test.cpp
Go to the documentation of this file.
1/** \file adcTracker_test.cpp
2 * \brief Catch2 tests for the adcTracker app.
3 * \author Jared R. Males (jaredmales@gmail.com)
4 *
5 * \ingroup adcTracker_files
6 */
7
8/** \defgroup adcTracker_unit_test adcTracker Unit Tests
9 * \brief Unit tests for the adcTracker application.
10 *
11 * \ingroup application_unit_test
12 */
13
14#include "../../../tests/catch2/catch.hpp"
15#include "../adcTracker.hpp"
16#include "../../../tests/testMacrosINDI.hpp"
17#include "../../../tests/testXWC.hpp"
18
19#include <filesystem>
20#include <fstream>
21#include <limits>
22
23namespace libXWCTest
24{
25namespace adcTrackerTest
26{
27
28namespace
29{
30/// Build a per-test scratch directory under /tmp.
31std::string testRoot( const std::string &name )
32{
33 std::string root = "/tmp/adcTracker_test/" + name;
34 std::filesystem::create_directories( root );
35 return root;
36}
37
38/// Write an ADC lookup table for a test case.
39void writeLookupTable( const std::string &calibDir, const std::string &contents )
40{
41 std::filesystem::create_directories( calibDir );
42
43 std::ofstream out( calibDir + "/adc_lookup_table.txt" );
44 out << contents;
45}
46
47} // namespace
48
49/// \cond DOXYGEN_SUPPRESS_TEST_HARNESS
50/// Test harness exposing adcTracker internals needed by the unit tests.
51class adcTracker_test : public MagAOX::app::adcTracker
52{
53 public:
54 /// Exception injection modes for the narrow test seams.
55 enum class exceptionMode
56 {
57 none, ///< No injected exception.
58 stdException, ///< Throw a standard exception.
59 nonStdException, ///< Throw a non-standard exception.
60 nonFinite ///< Return a non-finite value.
61 };
62
63 /// Construct a testable ADC tracker instance in the default callback-test scratch area.
64 adcTracker_test( const std::string &device ) : adcTracker_test( device, testRoot( "indi_callbacks" ) )
65 {
66 }
67
68 /// Construct a testable ADC tracker instance.
69 adcTracker_test( const std::string &device, const std::string &root )
70 {
71 m_configName = device;
72 m_basePath = root;
73 m_calibDir = root + "/calib";
74
75 std::filesystem::create_directories( m_calibDir );
76 std::filesystem::create_directories( root + "/telem" );
77
78 m_tel.logPath( root + "/telem" );
79 m_tel.logExt( "bintel" );
80 m_tel.logName( m_configName );
81 m_tel.m_logLevel = logPrio::LOG_TELEM;
82 m_maxInterval = 3600.0;
83
85 XWCTEST_SETUP_INDI_NEW_PROP( deltaAngle );
86 XWCTEST_SETUP_INDI_NEW_PROP( deltaADC1 );
87 XWCTEST_SETUP_INDI_NEW_PROP( deltaADC2 );
89
90 XWCTEST_SETUP_INDI_ARB_PROP( m_indiP_teldata, tcsi, teldata );
91 }
92
93 /// Run adcTracker startup.
94 int startup()
95 {
96 return appStartup();
97 }
98
99 /// Run adcTracker logic.
100 int logic()
101 {
102 return appLogic();
103 }
104
105 /// Run adcTracker shutdown.
106 int shutdown()
107 {
108 return appShutdown();
109 }
110
111 /// Invoke the telemeter callback directly.
112 int recordTelemDirect()
113 {
114 return recordTelem( nullptr );
115 }
116
117 /// Invoke the ADC 1 send helper directly.
118 int sendADC1PositionDirect( float adc1 )
119 {
121 }
122
123 /// Invoke the ADC 2 send helper directly.
124 int sendADC2PositionDirect( float adc2 )
125 {
127 }
128
129 /// Return whether the lookup table was initialized successfully.
130 bool lookupReady() const
131 {
132 return m_lookupReady;
133 }
134
135 /// Return the configured maximum lookup zenith distance.
136 float maxZD() const
137 {
138 return m_maxZD;
139 }
140
141 /// Return the configured lookup-table filename.
142 std::string lookupFile() const
143 {
144 return m_lookupFile;
145 }
146
147 /// Return the configured ADC 1 zero position.
148 float adc1zero() const
149 {
150 return m_adc1zero;
151 }
152
153 /// Return the configured ADC 1 lookup sign.
154 int adc1lupsign() const
155 {
156 return m_adc1lupsign;
157 }
158
159 /// Return the configured ADC 2 zero position.
160 float adc2zero() const
161 {
162 return m_adc2zero;
163 }
164
165 /// Return the configured ADC 2 lookup sign.
166 int adc2lupsign() const
167 {
168 return m_adc2lupsign;
169 }
170
171 /// Return the shared ADC delta angle.
172 float deltaAngle() const
173 {
174 return m_deltaAngle;
175 }
176
177 /// Return the ADC 1 delta angle.
178 float adc1delta() const
179 {
180 return m_adc1delta;
181 }
182
183 /// Return the ADC 2 delta angle.
184 float adc2delta() const
185 {
186 return m_adc2delta;
187 }
188
189 /// Return the configured minimum zenith distance threshold.
190 float minZD() const
191 {
192 return m_minZD;
193 }
194
195 /// Return the configured ADC 1 device name.
196 std::string adc1DevName() const
197 {
198 return m_adc1DevName;
199 }
200
201 /// Return the configured ADC 2 device name.
202 std::string adc2DevName() const
203 {
204 return m_adc2DevName;
205 }
206
207 /// Return the configured TCS device name.
208 std::string tcsDevName() const
209 {
210 return m_tcsDevName;
211 }
212
213 /// Return the configured update interval.
214 float updateInterval() const
215 {
216 return m_updateInterval;
217 }
218
219 /// Return whether tracking is currently enabled.
220 bool tracking() const
221 {
222 return m_tracking;
223 }
224
225 /// Return the most recent received zenith distance.
226 float zd() const
227 {
228 return m_zd;
229 }
230
231 /// Return whether a valid zenith distance has been received.
232 bool haveZD() const
233 {
234 return m_haveZD;
235 }
236
237 /// Return whether the below-minZD status switch is on.
238 bool belowMinZDStatus() const
239 {
240 return m_indiP_belowMinZD["state"].getSwitchState() == pcf::IndiElement::On;
241 }
242
243 /// Return whether the above-maxZD status switch is on.
244 bool aboveMaxZDStatus() const
245 {
246 return m_indiP_aboveMaxZD["state"].getSwitchState() == pcf::IndiElement::On;
247 }
248
249 /// Return the last ADC update timestamp.
250 double lastUpdate() const
251 {
252 return m_lastUpdate;
253 }
254
255 /// Return the current FSM state.
257 {
258 return state();
259 }
260
261 /// Set whether tracking is enabled.
262 void setTracking( bool tracking )
263 {
264 m_tracking = tracking;
265 }
266
267 /// Set the current test zenith distance and validity flag.
268 void setZD( float zd, bool haveZD = true )
269 {
270 m_zd = zd;
271 m_haveZD = haveZD;
272 }
273
274 /// Set the last-update timestamp used by appLogic.
275 void setLastUpdate( double lastUpdate )
276 {
277 m_lastUpdate = lastUpdate;
278 }
279
280 /// Set the update interval used by appLogic.
281 void setUpdateInterval( float updateInterval )
282 {
283 m_updateInterval = updateInterval;
284 }
285
286 /// Set the shared and per-ADC tracking offsets.
287 void setOffsets( float deltaAngle, float adc1delta, float adc2delta )
288 {
289 m_deltaAngle = deltaAngle;
290 m_adc1delta = adc1delta;
291 m_adc2delta = adc2delta;
292 }
293
294 /// Set the ADC zero positions used by appLogic.
295 void setZeros( float adc1zero, float adc2zero )
296 {
297 m_adc1zero = adc1zero;
298 m_adc2zero = adc2zero;
299 }
300
301 /// Set the ADC lookup-table signs used by appLogic.
302 void setSigns( int adc1sign, int adc2sign )
303 {
304 m_adc1lupsign = adc1sign;
305 m_adc2lupsign = adc2sign;
306 }
307
308 /// Set the minimum zenith distance threshold.
309 void setMinZDValue( float minZD )
310 {
311 m_minZD = minZD;
312 }
313
314 /// Set up the standard config entries on the test configurator.
315 void setupTestConfig()
316 {
317 setupConfig();
318 }
319
320 /// Read a config file into the app's internal configurator.
321 void readConfigFile( const std::string &path )
322 {
323 config.readConfig( path );
324 }
325
326 /// Load configuration values from the app's internal configurator.
327 void loadOwnConfig()
328 {
329 loadConfig();
330 }
331
332 /// Apply a tracking toggle callback payload.
333 int applyTracking( bool enabled )
334 {
335 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
336 ip.setDevice( m_configName );
337 ip.setName( "tracking" );
338 ip.add( pcf::IndiElement( "toggle" ) );
339 ip["toggle"].setSwitchState( enabled ? pcf::IndiElement::On : pcf::IndiElement::Off );
340
341 return newCallBack_m_indiP_tracking( ip );
342 }
343
344 /// Apply a shared delta-angle callback payload.
345 int applyDeltaAngle( float value )
346 {
347 pcf::IndiProperty ip( pcf::IndiProperty::Number );
348 ip.setDevice( m_configName );
349 ip.setName( "deltaAngle" );
350 ip.add( pcf::IndiElement( "target" ) );
351 ip["target"].set( value );
352
353 return newCallBack_m_indiP_deltaAngle( ip );
354 }
355
356 /// Apply an ADC 1 delta-angle callback payload.
357 int applyDeltaADC1( float value )
358 {
359 pcf::IndiProperty ip( pcf::IndiProperty::Number );
360 ip.setDevice( m_configName );
361 ip.setName( "deltaADC1" );
362 ip.add( pcf::IndiElement( "target" ) );
363 ip["target"].set( value );
364
365 return newCallBack_m_indiP_deltaADC1( ip );
366 }
367
368 /// Apply an ADC 2 delta-angle callback payload.
369 int applyDeltaADC2( float value )
370 {
371 pcf::IndiProperty ip( pcf::IndiProperty::Number );
372 ip.setDevice( m_configName );
373 ip.setName( "deltaADC2" );
374 ip.add( pcf::IndiElement( "target" ) );
375 ip["target"].set( value );
376
377 return newCallBack_m_indiP_deltaADC2( ip );
378 }
379
380 /// Apply a minimum-ZD callback payload.
381 int applyMinZD( float value )
382 {
383 pcf::IndiProperty ip( pcf::IndiProperty::Number );
384 ip.setDevice( m_configName );
385 ip.setName( "minZD" );
386 ip.add( pcf::IndiElement( "target" ) );
387 ip["target"].set( value );
388
389 return newCallBack_m_indiP_minZD( ip );
390 }
391
392 /// Apply a teldata callback payload.
393 int applyTeldataZD( float value )
394 {
395 pcf::IndiProperty ip( pcf::IndiProperty::Number );
396 ip.setDevice( "tcsi" );
397 ip.setName( "teldata" );
398 ip.add( pcf::IndiElement( "zd" ) );
399 ip["zd"].set( value );
400
401 return setCallBack_m_indiP_teldata( ip );
402 }
403
404 /// Invoke the static tracking callback wrapper.
405 int staticTrackingCallback( const pcf::IndiProperty &ip )
406 {
407 return st_newCallBack_m_indiP_tracking( this, ip );
408 }
409
410 /// Invoke the static deltaAngle callback wrapper.
411 int staticDeltaAngleCallback( const pcf::IndiProperty &ip )
412 {
413 return st_newCallBack_m_indiP_deltaAngle( this, ip );
414 }
415
416 /// Invoke the static deltaADC1 callback wrapper.
417 int staticDeltaADC1Callback( const pcf::IndiProperty &ip )
418 {
419 return st_newCallBack_m_indiP_deltaADC1( this, ip );
420 }
421
422 /// Invoke the static deltaADC2 callback wrapper.
423 int staticDeltaADC2Callback( const pcf::IndiProperty &ip )
424 {
425 return st_newCallBack_m_indiP_deltaADC2( this, ip );
426 }
427
428 /// Invoke the static minZD callback wrapper.
429 int staticMinZDCallback( const pcf::IndiProperty &ip )
430 {
431 return st_newCallBack_m_indiP_minZD( this, ip );
432 }
433
434 /// Invoke the static teldata callback wrapper.
435 int staticTeldataCallback( const pcf::IndiProperty &ip )
436 {
437 return st_setCallBack_m_indiP_teldata( this, ip );
438 }
439
440 /// Clear any previously captured outbound ADC command.
441 void resetSent()
442 {
443 m_sendCount = 0;
444 m_lastADC1 = 0;
445 m_lastADC2 = 0;
446 }
447
448 /// Return how many outbound ADC commands were captured.
449 int sendCount() const
450 {
451 return m_sendCount;
452 }
453
454 /// Return the last ADC 1 command captured by the test harness.
455 float lastADC1() const
456 {
457 return m_lastADC1;
458 }
459
460 /// Return the last ADC 2 command captured by the test harness.
461 float lastADC2() const
462 {
463 return m_lastADC2;
464 }
465
466 /// Configure exception injection for startup interpolator setup.
467 void setSetupInterpolatorExceptionMode( exceptionMode mode )
468 {
469 m_setupInterpolatorExceptionMode = mode;
470 }
471
472 /// Configure exception injection for appLogic interpolation.
473 void setInterpolationExceptionMode( exceptionMode mode )
474 {
475 m_interpolationExceptionMode = mode;
476 }
477
478 /// Configure exception injection for teldata ZD extraction.
479 void setExtractZDExceptionMode( exceptionMode mode )
480 {
481 m_extractZDExceptionMode = mode;
482 }
483
484 /// Configure failure injection for ADC 1 sends.
485 void setADC1SendFailure( bool shouldFail )
486 {
487 m_failADC1Send = shouldFail;
488 }
489
490 /// Configure failure injection for ADC 2 sends.
491 void setADC2SendFailure( bool shouldFail )
492 {
493 m_failADC2Send = shouldFail;
494 }
495
496 /// Lock the INDI mutex for a test scope.
497 std::unique_lock<std::mutex> lockIndiMutex()
498 {
499 return std::unique_lock<std::mutex>( m_indiMutex );
500 }
501
502 protected:
503 /// Capture outbound ADC 1 commands instead of sending them over INDI.
504 int sendADC1Position( float adc1 ) override
505 {
506 if( m_failADC1Send )
507 {
508 return -1;
509 }
510
511 m_lastADC1 = adc1;
512 ++m_sendCount;
513
514 return 0;
515 }
516
517 /// Capture outbound ADC 2 commands instead of sending them over INDI.
518 int sendADC2Position( float adc2 ) override
519 {
520 if( m_failADC2Send )
521 {
522 return -1;
523 }
524
525 m_lastADC2 = adc2;
526 ++m_sendCount;
527
528 return 0;
529 }
530
531 /// Optionally throw while setting up interpolators to exercise startup exception handling.
532 void setupInterpolators() override
533 {
534 if( m_setupInterpolatorExceptionMode == exceptionMode::stdException )
535 {
536 throw std::runtime_error( "test setupInterpolators std::exception" );
537 }
538
539 if( m_setupInterpolatorExceptionMode == exceptionMode::nonStdException )
540 {
541 throw 1;
542 }
543
545 }
546
547 /// Optionally throw while interpolating ADC 1 to exercise appLogic exception handling.
548 float interpolateADC1( float zd ) override
549 {
550 if( m_interpolationExceptionMode == exceptionMode::stdException )
551 {
552 throw std::runtime_error( "test interpolateADC1 std::exception" );
553 }
554
555 if( m_interpolationExceptionMode == exceptionMode::nonStdException )
556 {
557 throw 1;
558 }
559
561 }
562
563 /// Preserve the production ADC 2 interpolation logic in the test harness.
564 float interpolateADC2( float zd ) override
565 {
567 }
568
569 /// Optionally throw while extracting teldata.zd to exercise callback exception handling.
570 float extractZD( const pcf::IndiProperty &ipRecv ) override
571 {
572 if( m_extractZDExceptionMode == exceptionMode::stdException )
573 {
574 throw std::runtime_error( "test extractZD std::exception" );
575 }
576
577 if( m_extractZDExceptionMode == exceptionMode::nonStdException )
578 {
579 throw 1;
580 }
581
582 if( m_extractZDExceptionMode == exceptionMode::nonFinite )
583 {
584 return std::numeric_limits<float>::infinity();
585 }
586
587 return MagAOX::app::adcTracker::extractZD( ipRecv );
588 }
589
590 private:
591 float m_lastADC1{ 0 }; ///< Last ADC 1 target captured by the test harness.
592 float m_lastADC2{ 0 }; ///< Last ADC 2 target captured by the test harness.
593 int m_sendCount{ 0 }; ///< Number of outbound ADC commands captured by the test harness.
594 bool m_failADC1Send{ false }; ///< True to force ADC 1 send failure in appLogic tests.
595 bool m_failADC2Send{ false }; ///< True to force ADC 2 send failure in appLogic tests.
596 exceptionMode m_setupInterpolatorExceptionMode{ exceptionMode::none }; ///< Startup exception injection state.
597 exceptionMode m_interpolationExceptionMode{ exceptionMode::none }; ///< appLogic interpolation exception injection.
598 exceptionMode m_extractZDExceptionMode{ exceptionMode::none }; ///< teldata extraction exception injection state.
599};
600/// \endcond
601
602/// Validate the adcTracker INDI callback wiring.
603/**
604 * \ingroup adcTracker_unit_test
605 */
606SCENARIO( "INDI callbacks validate their source properties", "[adcTracker]" )
607{
608 // clang-format off
609#ifdef ADCTRACK_TEST_DOXYGEN_REF
610 MagAOX::app::adcTracker::newCallBack_m_indiP_tracking( pcf::IndiProperty( pcf::IndiProperty::Switch ) );
611 MagAOX::app::adcTracker::newCallBack_m_indiP_deltaAngle( pcf::IndiProperty( pcf::IndiProperty::Number ) );
612 MagAOX::app::adcTracker::newCallBack_m_indiP_deltaADC1( pcf::IndiProperty( pcf::IndiProperty::Number ) );
613 MagAOX::app::adcTracker::newCallBack_m_indiP_deltaADC2( pcf::IndiProperty( pcf::IndiProperty::Number ) );
614 MagAOX::app::adcTracker::newCallBack_m_indiP_minZD( pcf::IndiProperty( pcf::IndiProperty::Number ) );
615 MagAOX::app::adcTracker::setCallBack_m_indiP_teldata( pcf::IndiProperty( pcf::IndiProperty::Number ) );
616#endif
617 // clang-format on
618
619 adcTracker_test tracker( "adcTracker_validation" );
620
621 WHEN( "the tracking callback receives the wrong device" )
622 {
623 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
624 ip.setDevice( "wrong" );
625 ip.setName( "tracking" );
626 ip.add( pcf::IndiElement( "toggle" ) );
627 ip["toggle"].setSwitchState( pcf::IndiElement::On );
628
629 REQUIRE( tracker.newCallBack_m_indiP_tracking( ip ) == -1 );
630 }
631
632 WHEN( "the deltaAngle callback receives the wrong property name" )
633 {
634 pcf::IndiProperty ip( pcf::IndiProperty::Number );
635 ip.setDevice( "adcTracker_validation" );
636 ip.setName( "wrong" );
637 ip.add( pcf::IndiElement( "target" ) );
638 ip["target"].set( 1.0f );
639
640 REQUIRE( tracker.newCallBack_m_indiP_deltaAngle( ip ) == -1 );
641 }
642
643 WHEN( "the deltaADC1 callback receives a valid payload" )
644 {
645 pcf::IndiProperty ip( pcf::IndiProperty::Number );
646 ip.setDevice( "adcTracker_validation" );
647 ip.setName( "deltaADC1" );
648 ip.add( pcf::IndiElement( "target" ) );
649 ip["target"].set( 2.0f );
650
651 REQUIRE( tracker.newCallBack_m_indiP_deltaADC1( ip ) == 0 );
652 }
653
654 WHEN( "the deltaADC2 callback receives a valid payload" )
655 {
656 pcf::IndiProperty ip( pcf::IndiProperty::Number );
657 ip.setDevice( "adcTracker_validation" );
658 ip.setName( "deltaADC2" );
659 ip.add( pcf::IndiElement( "target" ) );
660 ip["target"].set( 3.0f );
661
662 REQUIRE( tracker.newCallBack_m_indiP_deltaADC2( ip ) == 0 );
663 }
664
665 WHEN( "the minZD callback receives a valid payload" )
666 {
667 pcf::IndiProperty ip( pcf::IndiProperty::Number );
668 ip.setDevice( "adcTracker_validation" );
669 ip.setName( "minZD" );
670 ip.add( pcf::IndiElement( "target" ) );
671 ip["target"].set( 4.0f );
672
673 REQUIRE( tracker.newCallBack_m_indiP_minZD( ip ) == 0 );
674 }
675
676 WHEN( "the teldata callback receives the wrong device" )
677 {
678 pcf::IndiProperty ip( pcf::IndiProperty::Number );
679 ip.setDevice( "wrong" );
680 ip.setName( "teldata" );
681 ip.add( pcf::IndiElement( "zd" ) );
682 ip["zd"].set( 5.0f );
683
684 REQUIRE( tracker.setCallBack_m_indiP_teldata( ip ) == -1 );
685 }
686
687 WHEN( "the teldata callback receives a valid payload" )
688 {
689 pcf::IndiProperty ip( pcf::IndiProperty::Number );
690 ip.setDevice( "tcsi" );
691 ip.setName( "teldata" );
692 ip.add( pcf::IndiElement( "zd" ) );
693 ip["zd"].set( 6.0f );
694
695 REQUIRE( tracker.setCallBack_m_indiP_teldata( ip ) == 0 );
696 }
697}
698
699/// Validate adcTracker configuration loading for defaults and explicit overrides.
700/**
701 * \ingroup adcTracker_unit_test
702 */
703SCENARIO( "adcTracker configuration loading preserves defaults and applies overrides", "[adcTracker]" )
704{
705 // clang-format off
706#ifdef ADCTRACK_TEST_DOXYGEN_REF
709#endif
710 // clang-format on
711
712 GIVEN( "an empty config file after setupConfig" )
713 {
714 std::string root = testRoot( "config_defaults" );
715 mx::app::writeConfigFile( root + "/adcTracker_test.conf", {}, {}, {} );
716
717 adcTracker_test tracker( "adcTracker_config_defaults", root );
718 tracker.setupTestConfig();
719 tracker.readConfigFile( root + "/adcTracker_test.conf" );
720
721 tracker.loadOwnConfig();
722
723 REQUIRE( tracker.lookupFile() == "adc_lookup_table.txt" );
724 REQUIRE( tracker.adc1zero() == Approx( 0.0f ) );
725 REQUIRE( tracker.adc1lupsign() == 1 );
726 REQUIRE( tracker.adc2zero() == Approx( 0.0f ) );
727 REQUIRE( tracker.adc2lupsign() == 1 );
728 REQUIRE( tracker.deltaAngle() == Approx( 0.0f ) );
729 REQUIRE( tracker.adc1delta() == Approx( 0.0f ) );
730 REQUIRE( tracker.adc2delta() == Approx( 0.0f ) );
731 REQUIRE( tracker.minZD() == Approx( 5.1f ) );
732 REQUIRE( tracker.adc1DevName() == "stageadc1" );
733 REQUIRE( tracker.adc2DevName() == "stageadc2" );
734 REQUIRE( tracker.tcsDevName() == "tcsi" );
735 REQUIRE( tracker.updateInterval() == Approx( 10.0f ) );
736 }
737
738 GIVEN( "a config file overriding the ADC tracker parameters" )
739 {
740 std::string root = testRoot( "config_overrides" );
741 mx::app::writeConfigFile( root + "/adcTracker_test.conf",
742 { "adcs",
743 "adcs",
744 "adcs",
745 "adcs",
746 "adcs",
747 "adcs",
748 "adcs",
749 "adcs",
750 "adcs",
751 "adcs",
752 "adcs",
753 "tcs",
754 "tracking" },
755 { "lookupFile",
756 "adc1zero",
757 "adc1lupsign",
758 "adc2zero",
759 "adc2lupsign",
760 "deltaAngle",
761 "adc1delta",
762 "adc2delta",
763 "minZD",
764 "adc1DevName",
765 "adc2DevName",
766 "devName",
767 "updateInterval" },
768 { "custom_lookup.txt",
769 "12.5",
770 "-1",
771 "-7.5",
772 "-1",
773 "4.5",
774 "1.5",
775 "-2.5",
776 "8.2",
777 "adc1custom",
778 "adc2custom",
779 "tcsi_custom",
780 "3.5" } );
781
782 adcTracker_test tracker( "adcTracker_config_overrides", root );
783 tracker.setupTestConfig();
784 tracker.readConfigFile( root + "/adcTracker_test.conf" );
785
786 tracker.loadOwnConfig();
787
788 REQUIRE( tracker.lookupFile() == "custom_lookup.txt" );
789 REQUIRE( tracker.adc1zero() == Approx( 12.5f ) );
790 REQUIRE( tracker.adc1lupsign() == -1 );
791 REQUIRE( tracker.adc2zero() == Approx( -7.5f ) );
792 REQUIRE( tracker.adc2lupsign() == -1 );
793 REQUIRE( tracker.deltaAngle() == Approx( 4.5f ) );
794 REQUIRE( tracker.adc1delta() == Approx( 1.5f ) );
795 REQUIRE( tracker.adc2delta() == Approx( -2.5f ) );
796 REQUIRE( tracker.minZD() == Approx( 8.2f ) );
797 REQUIRE( tracker.adc1DevName() == "adc1custom" );
798 REQUIRE( tracker.adc2DevName() == "adc2custom" );
799 REQUIRE( tracker.tcsDevName() == "tcsi_custom" );
800 REQUIRE( tracker.updateInterval() == Approx( 3.5f ) );
801 }
802}
803
804/// Validate adcTracker callback behavior for valid payloads.
805/**
806 * \ingroup adcTracker_unit_test
807 */
808SCENARIO( "adcTracker callbacks update runtime state from valid INDI payloads", "[adcTracker]" )
809{
810 // clang-format off
811#ifdef ADCTRACK_TEST_DOXYGEN_REF
812 MagAOX::app::adcTracker::newCallBack_m_indiP_tracking( pcf::IndiProperty( pcf::IndiProperty::Switch ) );
813 MagAOX::app::adcTracker::newCallBack_m_indiP_deltaAngle( pcf::IndiProperty( pcf::IndiProperty::Number ) );
814 MagAOX::app::adcTracker::newCallBack_m_indiP_deltaADC1( pcf::IndiProperty( pcf::IndiProperty::Number ) );
815 MagAOX::app::adcTracker::newCallBack_m_indiP_deltaADC2( pcf::IndiProperty( pcf::IndiProperty::Number ) );
816 MagAOX::app::adcTracker::newCallBack_m_indiP_minZD( pcf::IndiProperty( pcf::IndiProperty::Number ) );
817 MagAOX::app::adcTracker::setCallBack_m_indiP_teldata( pcf::IndiProperty( pcf::IndiProperty::Number ) );
818#endif
819 // clang-format on
820
821 std::string root = testRoot( "callback_behavior" );
822 adcTracker_test tracker( "adcTracker_callback_behavior", root );
823
824 GIVEN( "a tracking callback payload" )
825 {
826 tracker.setLastUpdate( 42.0 );
827
828 REQUIRE( tracker.applyTracking( true ) == 0 );
829 REQUIRE( tracker.tracking() == true );
830 REQUIRE( tracker.lastUpdate() == Approx( 0.0 ) );
831
832 tracker.setLastUpdate( 84.0 );
833
834 REQUIRE( tracker.applyTracking( false ) == 0 );
835 REQUIRE( tracker.tracking() == false );
836 REQUIRE( tracker.lastUpdate() == Approx( 0.0 ) );
837 }
838
839 GIVEN( "numeric offset callback payloads" )
840 {
841 REQUIRE( tracker.applyDeltaAngle( 7.25f ) == 0 );
842 REQUIRE( tracker.deltaAngle() == Approx( 7.25f ) );
843
844 REQUIRE( tracker.applyDeltaADC1( -1.5f ) == 0 );
845 REQUIRE( tracker.adc1delta() == Approx( -1.5f ) );
846
847 REQUIRE( tracker.applyDeltaADC2( 2.75f ) == 0 );
848 REQUIRE( tracker.adc2delta() == Approx( 2.75f ) );
849
850 REQUIRE( tracker.applyMinZD( 11.0f ) == 0 );
851 REQUIRE( tracker.minZD() == Approx( 11.0f ) );
852 }
853
854 GIVEN( "numeric callbacks missing their target element" )
855 {
856 pcf::IndiProperty deltaAngleIP( pcf::IndiProperty::Number );
857 deltaAngleIP.setDevice( "adcTracker_callback_behavior" );
858 deltaAngleIP.setName( "deltaAngle" );
859 REQUIRE( tracker.newCallBack_m_indiP_deltaAngle( deltaAngleIP ) == -1 );
860
861 pcf::IndiProperty deltaADC1IP( pcf::IndiProperty::Number );
862 deltaADC1IP.setDevice( "adcTracker_callback_behavior" );
863 deltaADC1IP.setName( "deltaADC1" );
864 REQUIRE( tracker.newCallBack_m_indiP_deltaADC1( deltaADC1IP ) == -1 );
865
866 pcf::IndiProperty deltaADC2IP( pcf::IndiProperty::Number );
867 deltaADC2IP.setDevice( "adcTracker_callback_behavior" );
868 deltaADC2IP.setName( "deltaADC2" );
869 REQUIRE( tracker.newCallBack_m_indiP_deltaADC2( deltaADC2IP ) == -1 );
870
871 pcf::IndiProperty minZDIP( pcf::IndiProperty::Number );
872 minZDIP.setDevice( "adcTracker_callback_behavior" );
873 minZDIP.setName( "minZD" );
874 REQUIRE( tracker.newCallBack_m_indiP_minZD( minZDIP ) == -1 );
875 }
876
877 GIVEN( "a tracking callback payload missing the toggle element" )
878 {
879 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
880 ip.setDevice( "adcTracker_callback_behavior" );
881 ip.setName( "tracking" );
882
883 REQUIRE( tracker.newCallBack_m_indiP_tracking( ip ) == 0 );
884 }
885
886 GIVEN( "a valid teldata.zd payload" )
887 {
888 REQUIRE( tracker.applyTeldataZD( 33.5f ) == 0 );
889 REQUIRE( tracker.zd() == Approx( 33.5f ) );
890 REQUIRE( tracker.haveZD() == true );
891 }
892
893 GIVEN( "a teldata payload missing the zd element" )
894 {
895 pcf::IndiProperty ip( pcf::IndiProperty::Number );
896 ip.setDevice( "tcsi" );
897 ip.setName( "teldata" );
898
899 REQUIRE( tracker.setCallBack_m_indiP_teldata( ip ) == 0 );
900 REQUIRE( tracker.haveZD() == false );
901 }
902}
903
904/// Validate adcTracker teldata callback exception handling.
905/**
906 * \ingroup adcTracker_unit_test
907 */
908SCENARIO( "adcTracker teldata callback handles extraction exceptions", "[adcTracker]" )
909{
910 // clang-format off
911#ifdef ADCTRACK_TEST_DOXYGEN_REF
912 MagAOX::app::adcTracker::setCallBack_m_indiP_teldata( pcf::IndiProperty( pcf::IndiProperty::Number ) );
913#endif
914 // clang-format on
915
916 GIVEN( "a valid teldata payload and an injected std::exception" )
917 {
918 adcTracker_test tracker( "adcTracker_teldata_std_exception", testRoot( "teldata_std_exception" ) );
919 tracker.setExtractZDExceptionMode( adcTracker_test::exceptionMode::stdException );
920
921 REQUIRE( tracker.applyTeldataZD( 11.0f ) == 0 );
922 REQUIRE( tracker.haveZD() == false );
923 }
924
925 GIVEN( "a valid teldata payload and an injected non-standard exception" )
926 {
927 adcTracker_test tracker( "adcTracker_teldata_unknown_exception", testRoot( "teldata_unknown_exception" ) );
928 tracker.setExtractZDExceptionMode( adcTracker_test::exceptionMode::nonStdException );
929
930 REQUIRE( tracker.applyTeldataZD( 11.0f ) == 0 );
931 REQUIRE( tracker.haveZD() == false );
932 }
933
934 GIVEN( "a valid teldata payload and an injected non-finite zenith distance" )
935 {
936 adcTracker_test tracker( "adcTracker_teldata_nonfinite", testRoot( "teldata_nonfinite" ) );
937 tracker.setExtractZDExceptionMode( adcTracker_test::exceptionMode::nonFinite );
938
939 REQUIRE( tracker.applyTeldataZD( 11.0f ) == 0 );
940 }
941}
942
943/// Validate adcTracker startup against valid and invalid lookup tables.
944/**
945 * \ingroup adcTracker_unit_test
946 */
947SCENARIO( "adcTracker startup validates the lookup table before running", "[adcTracker]" )
948{
949 // clang-format off
950#ifdef ADCTRACK_TEST_DOXYGEN_REF
952#endif
953 // clang-format on
954
955 GIVEN( "a valid lookup table" )
956 {
957 std::string root = testRoot( "startup_valid" );
958 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
959
960 adcTracker_test tracker( "adcTracker_test_valid", root );
961
962 REQUIRE( tracker.startup() == 0 );
963 REQUIRE( tracker.lookupReady() );
964 REQUIRE( tracker.maxZD() == Approx( 20.0f ) );
965 REQUIRE( tracker.belowMinZDStatus() == false );
966 REQUIRE( tracker.aboveMaxZDStatus() == false );
967 REQUIRE( tracker.fsmState() == MagAOX::app::stateCodes::READY );
968 }
969
970 GIVEN( "a missing lookup table" )
971 {
972 std::string root = testRoot( "startup_missing" );
973 adcTracker_test tracker( "adcTracker_test_missing", root );
974
975 REQUIRE( tracker.startup() == -1 );
976 REQUIRE( tracker.lookupReady() == false );
977 }
978
979 GIVEN( "a lookup table with too few rows" )
980 {
981 std::string root = testRoot( "startup_too_few_rows" );
982 writeLookupTable( root + "/calib", "0,0,0\n" );
983
984 adcTracker_test tracker( "adcTracker_test_too_few_rows", root );
985
986 REQUIRE( tracker.startup() == -1 );
987 REQUIRE( tracker.lookupReady() == false );
988 }
989
990 GIVEN( "a lookup table with a malformed row" )
991 {
992 std::string root = testRoot( "startup_malformed_row" );
993 writeLookupTable( root + "/calib", "0,0,0\n10,10\n20,20,40\n" );
994
995 adcTracker_test tracker( "adcTracker_test_malformed_row", root );
996
997 REQUIRE( tracker.startup() == -1 );
998 REQUIRE( tracker.lookupReady() == false );
999 }
1000
1001 GIVEN( "a lookup table with non-monotonic zenith-distance samples" )
1002 {
1003 std::string root = testRoot( "startup_non_monotonic" );
1004 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n10,20,40\n" );
1005
1006 adcTracker_test tracker( "adcTracker_test_non_monotonic", root );
1007
1008 REQUIRE( tracker.startup() == -1 );
1009 REQUIRE( tracker.lookupReady() == false );
1010 }
1011
1012 GIVEN( "a valid lookup table and an injected std::exception during interpolator setup" )
1013 {
1014 std::string root = testRoot( "startup_interp_std_exception" );
1015 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1016
1017 adcTracker_test tracker( "adcTracker_test_interp_std_exception", root );
1018 tracker.setSetupInterpolatorExceptionMode( adcTracker_test::exceptionMode::stdException );
1019
1020 REQUIRE( tracker.startup() == -1 );
1021 REQUIRE( tracker.lookupReady() == false );
1022 }
1023
1024 GIVEN( "a valid lookup table and an injected non-standard exception during interpolator setup" )
1025 {
1026 std::string root = testRoot( "startup_interp_unknown_exception" );
1027 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1028
1029 adcTracker_test tracker( "adcTracker_test_interp_unknown_exception", root );
1030 tracker.setSetupInterpolatorExceptionMode( adcTracker_test::exceptionMode::nonStdException );
1031
1032 REQUIRE( tracker.startup() == -1 );
1033 REQUIRE( tracker.lookupReady() == false );
1034 }
1035}
1036
1037/// Validate adcTracker appLogic dispatch decisions and target calculations.
1038/**
1039 * \ingroup adcTracker_unit_test
1040 */
1041SCENARIO( "adcTracker appLogic sends ADC targets only when tracking data are ready", "[adcTracker]" )
1042{
1043 // clang-format off
1044#ifdef ADCTRACK_TEST_DOXYGEN_REF
1046#endif
1047 // clang-format on
1048
1049 WHEN( "tracking is disabled" )
1050 {
1051 std::string root = testRoot( "logic_tracking_disabled" );
1052 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1053
1054 adcTracker_test tracker( "adcTracker_logic_tracking_disabled", root );
1055 REQUIRE( tracker.startup() == 0 );
1056
1057 tracker.setUpdateInterval( 0.0f );
1058 tracker.setLastUpdate( 0.0 );
1059 tracker.setZeros( 1.0f, 2.0f );
1060 tracker.setSigns( 1, 1 );
1061 tracker.setOffsets( 5.0f, 3.0f, 4.0f );
1062 tracker.setMinZDValue( 5.1f );
1063 tracker.resetSent();
1064
1065 tracker.setTracking( false );
1066 tracker.setZD( 15.0f );
1067
1068 REQUIRE( tracker.logic() == 0 );
1069 REQUIRE( tracker.sendCount() == 0 );
1070 }
1071
1072 WHEN( "tracking is enabled before any valid zenith distance is received" )
1073 {
1074 std::string root = testRoot( "logic_no_zd" );
1075 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1076
1077 adcTracker_test tracker( "adcTracker_logic_no_zd", root );
1078 REQUIRE( tracker.startup() == 0 );
1079
1080 tracker.setUpdateInterval( 0.0f );
1081 tracker.setLastUpdate( 0.0 );
1082 tracker.setZeros( 1.0f, 2.0f );
1083 tracker.setSigns( 1, 1 );
1084 tracker.setOffsets( 5.0f, 3.0f, 4.0f );
1085 tracker.setMinZDValue( 5.1f );
1086 tracker.resetSent();
1087
1088 tracker.setTracking( true );
1089 tracker.setZD( 0.0f, false );
1090
1091 REQUIRE( tracker.logic() == 0 );
1092 REQUIRE( tracker.sendCount() == 0 );
1093 }
1094
1095 WHEN( "the INDI mutex is already locked by another scope" )
1096 {
1097 std::string root = testRoot( "logic_mutex_locked" );
1098 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1099
1100 adcTracker_test tracker( "adcTracker_logic_mutex_locked", root );
1101 REQUIRE( tracker.startup() == 0 );
1102
1103 tracker.setUpdateInterval( 0.0f );
1104 tracker.setLastUpdate( 0.0 );
1105 tracker.setTracking( true );
1106 tracker.setZD( 15.0f );
1107 tracker.resetSent();
1108
1109 auto lock = tracker.lockIndiMutex();
1110
1111 REQUIRE( tracker.logic() == 0 );
1112 REQUIRE( tracker.sendCount() == 0 );
1113 }
1114
1115 WHEN( "the zenith distance is within the lookup table range" )
1116 {
1117 std::string root = testRoot( "logic_interpolate" );
1118 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1119
1120 adcTracker_test tracker( "adcTracker_logic_interpolate", root );
1121 REQUIRE( tracker.startup() == 0 );
1122
1123 tracker.setUpdateInterval( 0.0f );
1124 tracker.setLastUpdate( 0.0 );
1125 tracker.setZeros( 1.0f, 2.0f );
1126 tracker.setSigns( 1, 1 );
1127 tracker.setOffsets( 5.0f, 3.0f, 4.0f );
1128 tracker.setMinZDValue( 5.1f );
1129 tracker.resetSent();
1130
1131 tracker.setTracking( true );
1132 tracker.setZD( 15.0f );
1133
1134 REQUIRE( tracker.logic() == 0 );
1135 REQUIRE( tracker.sendCount() == 2 );
1136 REQUIRE( tracker.lastADC1() == Approx( 24.0f ) );
1137 REQUIRE( tracker.lastADC2() == Approx( 41.0f ) );
1138 REQUIRE( tracker.belowMinZDStatus() == false );
1139 REQUIRE( tracker.aboveMaxZDStatus() == false );
1140 }
1141
1142 WHEN( "the zenith distance is below minZD" )
1143 {
1144 std::string root = testRoot( "logic_below_min_zd" );
1145 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1146
1147 adcTracker_test tracker( "adcTracker_logic_below_min_zd", root );
1148 REQUIRE( tracker.startup() == 0 );
1149
1150 tracker.setUpdateInterval( 0.0f );
1151 tracker.setLastUpdate( 0.0 );
1152 tracker.setZeros( 1.0f, 2.0f );
1153 tracker.setSigns( 1, 1 );
1154 tracker.setOffsets( 5.0f, 3.0f, 4.0f );
1155 tracker.setMinZDValue( 5.1f );
1156 tracker.resetSent();
1157
1158 tracker.setTracking( true );
1159 tracker.setZD( 2.0f );
1160
1161 REQUIRE( tracker.logic() == 0 );
1162 REQUIRE( tracker.sendCount() == 2 );
1163 REQUIRE( tracker.lastADC1() == Approx( 9.0f ) );
1164 REQUIRE( tracker.lastADC2() == Approx( 11.0f ) );
1165 REQUIRE( tracker.belowMinZDStatus() == true );
1166 REQUIRE( tracker.aboveMaxZDStatus() == false );
1167 }
1168
1169 WHEN( "the zenith distance is above the lookup-table maximum" )
1170 {
1171 std::string root = testRoot( "logic_above_max_zd" );
1172 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1173
1174 adcTracker_test tracker( "adcTracker_logic_above_max_zd", root );
1175 REQUIRE( tracker.startup() == 0 );
1176
1177 tracker.setUpdateInterval( 0.0f );
1178 tracker.setLastUpdate( 0.0 );
1179 tracker.setZeros( 1.0f, 2.0f );
1180 tracker.setSigns( 1, 1 );
1181 tracker.setOffsets( 5.0f, 3.0f, 4.0f );
1182 tracker.setMinZDValue( 5.1f );
1183 tracker.resetSent();
1184
1185 tracker.setTracking( true );
1186 tracker.setZD( 30.0f );
1187
1188 REQUIRE( tracker.logic() == 0 );
1189 REQUIRE( tracker.sendCount() == 2 );
1190 REQUIRE( tracker.lastADC1() == Approx( 29.0f ) );
1191 REQUIRE( tracker.lastADC2() == Approx( 51.0f ) );
1192 REQUIRE( tracker.belowMinZDStatus() == false );
1193 REQUIRE( tracker.aboveMaxZDStatus() == true );
1194 }
1195
1196 WHEN( "interpolation throws a std::exception" )
1197 {
1198 std::string root = testRoot( "logic_interpolation_std_exception" );
1199 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1200
1201 adcTracker_test tracker( "adcTracker_logic_interpolation_std_exception", root );
1202 REQUIRE( tracker.startup() == 0 );
1203
1204 tracker.setUpdateInterval( 0.0f );
1205 tracker.setLastUpdate( 0.0 );
1206 tracker.setZeros( 1.0f, 2.0f );
1207 tracker.setSigns( 1, 1 );
1208 tracker.setOffsets( 5.0f, 3.0f, 4.0f );
1209 tracker.setMinZDValue( 5.1f );
1210 tracker.setTracking( true );
1211 tracker.setZD( 15.0f );
1212 tracker.setInterpolationExceptionMode( adcTracker_test::exceptionMode::stdException );
1213 tracker.resetSent();
1214
1215 REQUIRE( tracker.logic() == 0 );
1216 REQUIRE( tracker.sendCount() == 0 );
1217 }
1218
1219 WHEN( "interpolation throws a non-standard exception" )
1220 {
1221 std::string root = testRoot( "logic_interpolation_unknown_exception" );
1222 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1223
1224 adcTracker_test tracker( "adcTracker_logic_interpolation_unknown_exception", root );
1225 REQUIRE( tracker.startup() == 0 );
1226
1227 tracker.setUpdateInterval( 0.0f );
1228 tracker.setLastUpdate( 0.0 );
1229 tracker.setZeros( 1.0f, 2.0f );
1230 tracker.setSigns( 1, 1 );
1231 tracker.setOffsets( 5.0f, 3.0f, 4.0f );
1232 tracker.setMinZDValue( 5.1f );
1233 tracker.setTracking( true );
1234 tracker.setZD( 15.0f );
1235 tracker.setInterpolationExceptionMode( adcTracker_test::exceptionMode::nonStdException );
1236 tracker.resetSent();
1237
1238 REQUIRE( tracker.logic() == 0 );
1239 REQUIRE( tracker.sendCount() == 0 );
1240 }
1241
1242 WHEN( "sending ADC 1 fails during appLogic" )
1243 {
1244 std::string root = testRoot( "logic_send_adc1_failure" );
1245 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1246
1247 adcTracker_test tracker( "adcTracker_logic_send_adc1_failure", root );
1248 REQUIRE( tracker.startup() == 0 );
1249
1250 tracker.setUpdateInterval( 0.0f );
1251 tracker.setLastUpdate( 0.0 );
1252 tracker.setZeros( 1.0f, 2.0f );
1253 tracker.setSigns( 1, 1 );
1254 tracker.setOffsets( 5.0f, 3.0f, 4.0f );
1255 tracker.setMinZDValue( 5.1f );
1256 tracker.setTracking( true );
1257 tracker.setZD( 15.0f );
1258 tracker.setADC1SendFailure( true );
1259 tracker.resetSent();
1260
1261 REQUIRE( tracker.logic() == 0 );
1262 REQUIRE( tracker.sendCount() == 0 );
1263 }
1264
1265 WHEN( "sending ADC 2 fails during appLogic" )
1266 {
1267 std::string root = testRoot( "logic_send_adc2_failure" );
1268 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1269
1270 adcTracker_test tracker( "adcTracker_logic_send_adc2_failure", root );
1271 REQUIRE( tracker.startup() == 0 );
1272
1273 tracker.setUpdateInterval( 0.0f );
1274 tracker.setLastUpdate( 0.0 );
1275 tracker.setZeros( 1.0f, 2.0f );
1276 tracker.setSigns( 1, 1 );
1277 tracker.setOffsets( 5.0f, 3.0f, 4.0f );
1278 tracker.setMinZDValue( 5.1f );
1279 tracker.setTracking( true );
1280 tracker.setZD( 15.0f );
1281 tracker.setADC2SendFailure( true );
1282 tracker.resetSent();
1283
1284 REQUIRE( tracker.logic() == 0 );
1285 REQUIRE( tracker.sendCount() == 1 );
1286 REQUIRE( tracker.belowMinZDStatus() == false );
1287 REQUIRE( tracker.aboveMaxZDStatus() == false );
1288 }
1289}
1290
1291/// Validate adcTracker shutdown and telemetry callback direct entry points.
1292/**
1293 * \ingroup adcTracker_unit_test
1294 */
1295SCENARIO( "adcTracker direct helper entry points behave as expected", "[adcTracker]" )
1296{
1297 // clang-format off
1298 #ifdef ADCTRACK_TEST_DOXYGEN_REF
1300 MagAOX::app::adcTracker::recordTelem( static_cast<const telem_adctrack *>( nullptr ) );
1303 #endif
1304 // clang-format on
1305
1306 GIVEN( "a tracker with its startup completed" )
1307 {
1308 std::string root = testRoot( "direct_helper_entry_points" );
1309 writeLookupTable( root + "/calib", "0,0,0\n10,10,20\n20,20,40\n" );
1310
1311 adcTracker_test tracker( "adcTracker_direct_helper_entry_points", root );
1312 REQUIRE( tracker.startup() == 0 );
1313
1314 WHEN( "appShutdown is called directly" )
1315 {
1316 REQUIRE( tracker.shutdown() == 0 );
1317 }
1318
1319 WHEN( "recordTelem is called directly" )
1320 {
1321 tracker.setTracking( true );
1322 tracker.setOffsets( 1.5f, -2.0f, 3.0f );
1323 tracker.setMinZDValue( 7.5f );
1324
1325 REQUIRE( tracker.recordTelemDirect() == 0 );
1326 }
1327
1328 WHEN( "the direct ADC send helpers are called without an initialized INDI driver" )
1329 {
1330 REQUIRE( tracker.sendADC1PositionDirect( 12.5f ) == -1 );
1331 REQUIRE( tracker.sendADC2PositionDirect( -7.25f ) == -1 );
1332 }
1333 }
1334}
1335
1336/// Validate adcTracker static callback wrappers forward to the instance callbacks.
1337/**
1338 * \ingroup adcTracker_unit_test
1339 */
1340SCENARIO( "adcTracker static callback wrappers forward correctly", "[adcTracker]" )
1341{
1342 adcTracker_test tracker( "adcTracker_static_wrappers", testRoot( "static_wrappers" ) );
1343
1344 GIVEN( "a valid tracking callback payload" )
1345 {
1346 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
1347 ip.setDevice( "adcTracker_static_wrappers" );
1348 ip.setName( "tracking" );
1349 ip.add( pcf::IndiElement( "toggle" ) );
1350 ip["toggle"].setSwitchState( pcf::IndiElement::On );
1351
1352 REQUIRE( tracker.staticTrackingCallback( ip ) == 0 );
1353 REQUIRE( tracker.tracking() == true );
1354 }
1355
1356 GIVEN( "a valid deltaAngle callback payload" )
1357 {
1358 pcf::IndiProperty ip( pcf::IndiProperty::Number );
1359 ip.setDevice( "adcTracker_static_wrappers" );
1360 ip.setName( "deltaAngle" );
1361 ip.add( pcf::IndiElement( "target" ) );
1362 ip["target"].set( 4.0f );
1363
1364 REQUIRE( tracker.staticDeltaAngleCallback( ip ) == 0 );
1365 REQUIRE( tracker.deltaAngle() == Approx( 4.0f ) );
1366 }
1367
1368 GIVEN( "a valid deltaADC1 callback payload" )
1369 {
1370 pcf::IndiProperty ip( pcf::IndiProperty::Number );
1371 ip.setDevice( "adcTracker_static_wrappers" );
1372 ip.setName( "deltaADC1" );
1373 ip.add( pcf::IndiElement( "target" ) );
1374 ip["target"].set( -1.0f );
1375
1376 REQUIRE( tracker.staticDeltaADC1Callback( ip ) == 0 );
1377 REQUIRE( tracker.adc1delta() == Approx( -1.0f ) );
1378 }
1379
1380 GIVEN( "a valid deltaADC2 callback payload" )
1381 {
1382 pcf::IndiProperty ip( pcf::IndiProperty::Number );
1383 ip.setDevice( "adcTracker_static_wrappers" );
1384 ip.setName( "deltaADC2" );
1385 ip.add( pcf::IndiElement( "target" ) );
1386 ip["target"].set( 2.5f );
1387
1388 REQUIRE( tracker.staticDeltaADC2Callback( ip ) == 0 );
1389 REQUIRE( tracker.adc2delta() == Approx( 2.5f ) );
1390 }
1391
1392 GIVEN( "a valid minZD callback payload" )
1393 {
1394 pcf::IndiProperty ip( pcf::IndiProperty::Number );
1395 ip.setDevice( "adcTracker_static_wrappers" );
1396 ip.setName( "minZD" );
1397 ip.add( pcf::IndiElement( "target" ) );
1398 ip["target"].set( 9.5f );
1399
1400 REQUIRE( tracker.staticMinZDCallback( ip ) == 0 );
1401 REQUIRE( tracker.minZD() == Approx( 9.5f ) );
1402 }
1403
1404 GIVEN( "a valid teldata callback payload" )
1405 {
1406 pcf::IndiProperty ip( pcf::IndiProperty::Number );
1407 ip.setDevice( "tcsi" );
1408 ip.setName( "teldata" );
1409 ip.add( pcf::IndiElement( "zd" ) );
1410 ip["zd"].set( 22.0f );
1411
1412 REQUIRE( tracker.staticTeldataCallback( ip ) == 0 );
1413 REQUIRE( tracker.zd() == Approx( 22.0f ) );
1414 REQUIRE( tracker.haveZD() == true );
1415 }
1416}
1417
1418} // namespace adcTrackerTest
1419} // namespace libXWCTest
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
The MagAO-X ADC Tracker.
virtual float interpolateADC1(float zd)
Interpolate the ADC 1 lookup-table value for a zenith distance.
int recordTelem(const telem_adctrack *)
virtual int appShutdown()
Shutdown the app.
virtual void setupInterpolators()
Initialize the ADC lookup interpolators from the loaded lookup-table data.
virtual float extractZD(const pcf::IndiProperty &ipRecv)
Extract the zenith distance from the incoming TCS INDI property.
virtual int sendADC1Position(float adc1)
Send the ADC 1 target command.
virtual void setupConfig()
Set up configuration entries.
virtual float interpolateADC2(float zd)
Interpolate the ADC 2 lookup-table value for a zenith distance.
virtual int appStartup()
Startup function.
virtual int sendADC2Position(float adc2)
Send the ADC 2 target command.
virtual int appLogic()
Implementation of the FSM for adcTracker.
virtual void loadConfig()
Load configuration values.
SCENARIO("INDI callbacks validate their source properties", "[adcTracker]")
Validate the adcTracker INDI callback wiring.
@ none
Don't publish.
static constexpr logPrioT LOG_TELEM
A telemetry recording.
Namespace for all libXWC tests.
@ READY
The device is ready for operation, but is not operating.
int16_t stateCodeT
The type of the state code.
Log entry recording ADC tracker operator-adjustable state.
#define XWCTEST_SETUP_INDI_ARB_PROP(varname, device, propname)
#define XWCTEST_SETUP_INDI_NEW_PROP(propname)