API
 
Loading...
Searching...
No Matches
zaberLowLevelBinary_test.cpp
Go to the documentation of this file.
1/** \file zaberLowLevelBinary_test.cpp
2 * \brief Catch2 tests for the zaberLowLevelBinary app.
3 * \author Jared R. Males (jaredmales@gmail.com)
4 *
5 * \ingroup zaberLowLevelBinary_files
6 */
7
8#include <filesystem>
9#include <fstream>
10
11extern "C"
12{
13#include "../zb_serial.c"
14}
15
16#include "../../../tests/testXWC.hpp"
17#include "../../../tests/testMacrosINDI.hpp"
18
19#include "../zaberLowLevelBinary.hpp"
20
21using namespace MagAOX::app;
22
23namespace libXWCTest
24{
25
26/** \defgroup zaberLowLevelBinary_unit_test zaberLowLevelBinary Unit Tests
27 * \brief Unit tests for the zaberLowLevelBinary application.
28 *
29 * \ingroup application_unit_test
30 */
31
32/// Namespace for `zaberLowLevelBinary` unit tests.
33/** \ingroup zaberLowLevelBinary_unit_test
34 */
35namespace zaberLowLevelBinaryTest
36{
37
38/// \cond DOXYGEN_SUPPRESS_TEST_HARNESS
39class zaberLowLevelBinary_test : public zaberLowLevelBinary
40{
41 public:
42 /// Construct the test harness and set up INDI callback fixtures.
43 zaberLowLevelBinary_test( const std::string &device )
44 {
45 m_configName = device;
46
49 XWCTEST_SETUP_INDI_NEW_PROP( req_home_all );
51 XWCTEST_SETUP_INDI_NEW_PROP( req_ehalt );
52 XWCTEST_SETUP_INDI_NEW_PROP( knob_enable );
53 }
54
55 /// Set up a single staged snapshot and INDI transport for power-off tests.
56 int setupPowerOffSnapshot( const std::string &stageName, long rawPos, bool parked, long maxPos, time_t lastHomed )
57 {
58 std::error_code ec;
59
60 m_testRoot = std::filesystem::temp_directory_path() / ( "zaberLowLevelBinary_test_" + m_configName );
61 std::filesystem::remove_all( m_testRoot, ec );
62
63 m_basePath = m_testRoot.string();
64 m_sysPath = ( m_testRoot / "sys" ).string();
65
66 std::filesystem::create_directories( m_testRoot / MAGAOX_driverFIFORelPath );
67 std::filesystem::create_directories( std::filesystem::path( m_sysPath ) / m_configName );
68
69 m_stages.emplace_back( this );
70 m_stages.back().name( stageName );
71 m_stages.back().serial( "serial0" );
72
73 {
74 std::ofstream stateOut( std::filesystem::path( m_sysPath ) / m_configName / stageName );
75 stateOut << rawPos << '\n' << parked << '\n' << maxPos << '\n' << lastHomed << '\n';
76 }
77
78 if( appStartup() < 0 )
79 {
80 return -1;
81 }
82
83 if( createINDIFIFOS() < 0 )
84 {
85 return -1;
86 }
87
88 m_indiDriver = new indiDriver<MagAOXAppT>( this, m_configName, "0", "0" );
89
90 return ( m_indiDriver && m_indiDriver->good() ) ? 0 : -1;
91 }
92
93 /// Configure a stage entry for discovery and recovery tests.
94 int addConfiguredStage( const std::string &stageName, const std::string &serial, int deviceAddress = -1 )
95 {
96 m_stages.emplace_back( this );
97 m_stages.back().name( stageName );
98 m_stages.back().serial( serial );
99 m_stages.back().deviceAddress( deviceAddress );
100
101 const size_t idx = m_stages.size() - 1;
102
103 m_stageName.insert( { stageName, idx } );
104 m_stageSerial.insert( { serial, idx } );
105
106 return 0;
107 }
108
109 /// Load a discovery snapshot through the production mapping code.
110 int loadDiscoverySnapshot( const std::vector<int> &addresses, const std::vector<std::string> &serials )
111 {
112 return loadStages( addresses, serials );
113 }
114
115 /// Set the cached device address for a configured stage.
116 int setDeviceAddressFor( size_t stageIndex, int deviceAddress )
117 {
118 m_stages.at( stageIndex ).deviceAddress( deviceAddress );
119 return 0;
120 }
121
122 /// Get the cached device address for a configured stage.
123 int deviceAddressFor( size_t stageIndex ) const
124 {
125 return m_stages.at( stageIndex ).deviceAddress();
126 }
127
128 /// Drive the recoverable error handler under test.
129 int recoverTransportError( bool devicePresent )
130 {
131 return recoverFromError( devicePresent );
132 }
133
134 /// Set the FSM state for recovery tests.
135 int setAppState( stateCodes::stateCodeT newState )
136 {
137 state( newState );
138 return 0;
139 }
140
141 /// Get the FSM state for recovery tests.
142 stateCodes::stateCodeT appState() const
143 {
144 return state();
145 }
146
147 /// Read the value of a text, number, or switch element from a test property.
148 std::string propertyValue( const pcf::IndiProperty &property, const std::string &element ) const
149 {
150 return property[element].getValue();
151 }
152
153 /// Get the current-position property value for a stage.
154 std::string currPosValue( const std::string &stageName ) const
155 {
156 return propertyValue( m_indiP_curr_pos, stageName );
157 }
158
159 /// Get the target-position property value for a stage.
160 std::string tgtPosValue( const std::string &stageName ) const
161 {
162 return propertyValue( m_indiP_tgt_pos, stageName );
163 }
164
165 /// Get the parked-state property value for a stage.
166 std::string parkedValue( const std::string &stageName ) const
167 {
168 return propertyValue( m_indiP_parked, stageName );
169 }
170
171 /// Get the last-homed property value for a stage.
172 std::string lastHomedValue( const std::string &stageName ) const
173 {
174 return propertyValue( m_indiP_lastHomed, stageName );
175 }
176
177 /// Get the max-position property value for a stage.
178 std::string maxPosValue( const std::string &stageName ) const
179 {
180 return propertyValue( m_indiP_max_pos, stageName );
181 }
182
183 /// Get the current-state property value for a stage.
184 std::string currStateValue( const std::string &stageName ) const
185 {
186 return propertyValue( m_indiP_curr_state, stageName );
187 }
188
189 /// Get the warning-switch property value for a stage.
190 std::string warnValue( const std::string &stageName ) const
191 {
192 return propertyValue( m_indiP_warn, stageName );
193 }
194
195 /// Invoke the power-off handling under test.
196 int doOnPowerOff()
197 {
198 return onPowerOff();
199 }
200
201 ~zaberLowLevelBinary_test() noexcept
202 {
203 std::error_code ec;
204
205 delete m_indiDriver;
206 m_indiDriver = nullptr;
207 std::filesystem::remove_all( m_testRoot, ec );
208 }
209
210 private:
211 std::filesystem::path m_testRoot; ///< Temporary directory backing the test FIFOs and state snapshot.
212};
213
214class zaberBinaryStage_test : public zaberBinaryStage<zaberLowLevelBinary_test>
215{
216 public:
217 /// Construct a test binary-stage helper.
218 zaberBinaryStage_test( zaberLowLevelBinary_test *parent ) : zaberBinaryStage<zaberLowLevelBinary_test>( parent )
219 {
220 }
221
222 /// Set the fields used to detect homing completion.
223 void setHomeState( bool homing, bool warnWR, long tgtPos, long rawPos, time_t lastHomed )
224 {
225 m_homing = homing;
226 m_warnWR = warnWR;
227 m_tgtPos = tgtPos;
228 m_rawPos = rawPos;
229 m_lastHomed.tv_sec = lastHomed;
230 m_lastHomed.tv_nsec = 0;
231 }
232
233 /// Invoke the last-home timestamp refresh logic under test.
234 int refreshLastHomed( bool wasHoming )
235 {
236 return updateLastHomed( wasHoming );
237 }
238
239 /// Get the stored last-home seconds value.
240 time_t lastHomedSec() const
241 {
242 return m_lastHomed.tv_sec;
243 }
244};
245/// \endcond
246
247/// Verify zaberLowLevelBinary callback validation, power-off snapshots, and homing timestamp refresh logic.
248/**
249 * \ingroup zaberLowLevelBinary_unit_test
250 */
251SCENARIO( "INDI Callbacks", "[zaberLowLevelBinary]" )
252{
253 // clang-format off
254 #ifdef ZABERLOWLEVELBINARY_TEST_DOXYGEN_REF
255 zaberLowLevelBinary::newCallBack_m_indiP_tgt_pos( pcf::IndiProperty() );
257 #endif
258 // clang-format on
259
266}
267
268SCENARIO( "Power-off INDI snapshot retains stage state", "[zaberLowLevelBinary]" )
269{
270 zaberLowLevelBinary_test zllbt( "zllbtest" );
271
272 REQUIRE( zllbt.setupPowerOffSnapshot( "stageA", 12345, true, 54321, 77 ) == 0 );
273
274 REQUIRE( zllbt.doOnPowerOff() == 0 );
275
276 REQUIRE( zllbt.currPosValue( "stageA" ) == "12345" );
277 REQUIRE( zllbt.tgtPosValue( "stageA" ) == "12345" );
278 REQUIRE( zllbt.parkedValue( "stageA" ) == "1" );
279 REQUIRE( zllbt.lastHomedValue( "stageA" ) == "77" );
280 REQUIRE( zllbt.maxPosValue( "stageA" ) == "54321" );
281 REQUIRE( zllbt.currStateValue( "stageA" ) == "POWEROFF" );
282 REQUIRE( zllbt.warnValue( "stageA" ) == "Off" );
283}
284
285SCENARIO( "Binary last-home timestamps refresh after homing completes", "[zaberLowLevelBinary]" )
286{
287 zaberLowLevelBinary_test zllbt( "zllbtest" );
288 zaberBinaryStage_test stage( &zllbt );
289
290 WHEN( "a homing sequence completes with a stale stored timestamp" )
291 {
292 stage.setHomeState( false, false, 0, 0, 77 );
293
294 REQUIRE( stage.refreshLastHomed( true ) == 0 );
295 REQUIRE( stage.lastHomedSec() != 77 );
296 }
297
298 WHEN( "the stage is merely idle at home with an existing timestamp" )
299 {
300 stage.setHomeState( false, false, 0, 0, 77 );
301
302 REQUIRE( stage.refreshLastHomed( false ) == 0 );
303 REQUIRE( stage.lastHomedSec() == 77 );
304 }
305}
306
307/// Verify discovery clears stale addresses and reports missing configured stages safely.
308/**
309 * \ingroup zaberLowLevelBinary_unit_test
310 */
311SCENARIO( "Binary discovery resets stale device addresses", "[zaberLowLevelBinary]" )
312{
313 // clang-format off
314 #ifdef ZABERLOWLEVELBINARY_TEST_DOXYGEN_REF
315 zaberLowLevelBinary::loadStages( std::declval<const std::vector<int> &>(), std::declval<const std::vector<std::string> &>() );
316 #endif
317 // clang-format on
318
319 zaberLowLevelBinary_test zllbt( "zllbtest" );
320
321 REQUIRE( zllbt.addConfiguredStage( "stagebs", "64040", 1 ) == 0 );
322 REQUIRE( zllbt.addConfiguredStage( "stageirf", "122400", 2 ) == 0 );
323
324 REQUIRE( zllbt.loadDiscoverySnapshot( { 1 }, { "64040" } ) == ZBC_CONNECTED );
325 REQUIRE( zllbt.deviceAddressFor( 0 ) == 1 );
326 REQUIRE( zllbt.deviceAddressFor( 1 ) < 1 );
327}
328
329/// Verify a later discovery pass can find a stage that was missing initially.
330/**
331 * \ingroup zaberLowLevelBinary_unit_test
332 */
333SCENARIO( "Binary discovery can find devices that appear later", "[zaberLowLevelBinary]" )
334{
335 zaberLowLevelBinary_test zllbt( "zllbtest_rediscover" );
336
337 REQUIRE( zllbt.addConfiguredStage( "stagebs", "64040" ) == 0 );
338 REQUIRE( zllbt.addConfiguredStage( "stageirf", "122400" ) == 0 );
339
340 REQUIRE( zllbt.loadDiscoverySnapshot( { 1 }, { "64040" } ) == ZBC_CONNECTED );
341 REQUIRE( zllbt.deviceAddressFor( 0 ) == 1 );
342 REQUIRE( zllbt.deviceAddressFor( 1 ) < 1 );
343
344 REQUIRE( zllbt.loadDiscoverySnapshot( { 1, 2 }, { "64040", "122400" } ) == ZBC_CONNECTED );
345 REQUIRE( zllbt.deviceAddressFor( 0 ) == 1 );
346 REQUIRE( zllbt.deviceAddressFor( 1 ) == 2 );
347}
348
349/// Verify communication failures drop the binary app back into reconnectable states.
350/**
351 * \ingroup zaberLowLevelBinary_unit_test
352 */
353SCENARIO( "Recoverable binary transport errors transition to reconnect states", "[zaberLowLevelBinary]" )
354{
355 // clang-format off
356 #ifdef ZABERLOWLEVELBINARY_TEST_DOXYGEN_REF
359 #endif
360 // clang-format on
361
362 SECTION( "A present tty returns the app to NOTCONNECTED" )
363 {
364 zaberLowLevelBinary_test zllbt( "zllbtest_present" );
365
366 REQUIRE( zllbt.setupPowerOffSnapshot( "stageA", 12345, true, 54321, 77 ) == 0 );
367 REQUIRE( zllbt.setDeviceAddressFor( 0, 1 ) == 0 );
368 REQUIRE( zllbt.setAppState( stateCodes::ERROR ) == 0 );
369
370 REQUIRE( zllbt.recoverTransportError( true ) == 0 );
371 REQUIRE( zllbt.appState() == stateCodes::NOTCONNECTED );
372 REQUIRE( zllbt.currStateValue( "stageA" ) == "NOTCONNECTED" );
373 }
374
375 SECTION( "A missing tty returns the app to NODEVICE" )
376 {
377 zaberLowLevelBinary_test zllbt( "zllbtest_missing" );
378
379 REQUIRE( zllbt.setupPowerOffSnapshot( "stageA", 12345, true, 54321, 77 ) == 0 );
380 REQUIRE( zllbt.setDeviceAddressFor( 0, 1 ) == 0 );
381 REQUIRE( zllbt.setAppState( stateCodes::ERROR ) == 0 );
382
383 REQUIRE( zllbt.recoverTransportError( false ) == 0 );
384 REQUIRE( zllbt.appState() == stateCodes::NODEVICE );
385 REQUIRE( zllbt.currStateValue( "stageA" ) == "NODEVICE" );
386 }
387}
388
389} // namespace zaberLowLevelBinaryTest
390
391} // namespace libXWCTest
A class to manage the details of one binary-protocol stage in a Zaber system.
The low-level binary-protocol Zaber controller.
int recoverFromError(bool devicePresent)
Recover from a binary-transport error without terminating the app.
virtual int onPowerOff()
Handle the transition into the powered-off state.
int loadStages()
Discover configured stages on the binary bus.
int resetConnection()
Reset the active binary connection bookkeeping.
#define MAGAOX_driverFIFORelPath
The relative path to the INDI driver FIFOs.
Definition paths.hpp:85
@ NODEVICE
No device exists for the application to control.
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
@ NOTCONNECTED
The application is not connected to the device or service.
#define XWCTEST_INDI_NEW_CALLBACK(testclass, propname)
Catch-2 tests for whether a NEW callback properly validates the input property properly.
SCENARIO("INDI Callbacks", "[zaberLowLevelBinary]")
Verify zaberLowLevelBinary callback validation, power-off snapshots, and homing timestamp refresh log...
Namespace for all libXWC tests.
int16_t stateCodeT
The type of the state code.
#define XWCTEST_SETUP_INDI_NEW_PROP(propname)
#define ZBC_CONNECTED