API
 
Loading...
Searching...
No Matches
zaberLowLevel_test.cpp
Go to the documentation of this file.
1/** \file zaberLowLevel_test.cpp
2 * \brief Catch2 tests for the zaberLowLevel app.
3 * \author Jared R. Males (jaredmales@gmail.com)
4 *
5 * \ingroup zaberLowLevel_files
6 */
7
8#include <filesystem>
9#include <fstream>
10
11// Direct include to avoid having to link separately
12extern "C"
13{
14#include "../za_serial.c"
15}
16
17#include "../../../tests/testXWC.hpp"
18#include "../../../tests/testMacrosINDI.hpp"
19
20#include "../zaberLowLevel.hpp"
21
22using namespace MagAOX::app;
23
24namespace libXWCTest
25{
26
27/** \defgroup zaberLowLevel_unit_test zaberLowLevel Unit Tests
28 * \brief Unit tests for the zaberLowLevel application.
29 *
30 * \ingroup application_unit_test
31 */
32
33/// Namespace for `zaberLowLevel` unit tests.
34/** \ingroup zaberLowLevel_unit_test
35 */
36namespace zaberLowLevelTest
37{
38
39/// \cond DOXYGEN_SUPPRESS_TEST_HARNESS
40class zaberLowLevel_test : public zaberLowLevel
41{
42 public:
43 /// Construct a testable low-level controller instance.
44 zaberLowLevel_test( const std::string &device )
45 {
46 m_configName = device;
47
50 XWCTEST_SETUP_INDI_NEW_PROP( req_home_all );
52 XWCTEST_SETUP_INDI_NEW_PROP( req_ehalt );
53 XWCTEST_SETUP_INDI_NEW_PROP( knob_enable );
54 XWCTEST_SETUP_INDI_NEW_PROP( led_enable );
55 }
56
57 /// Set up a single staged snapshot and INDI transport for power-off tests.
58 int setupPowerOffSnapshot( const std::string &stageName, long rawPos, bool parked, long maxPos, time_t lastHomed )
59 {
60 std::error_code ec;
61
62 m_testRoot = std::filesystem::temp_directory_path() / ( "zaberLowLevel_test_" + m_configName );
63 std::filesystem::remove_all( m_testRoot, ec );
64
65 m_basePath = m_testRoot.string();
66 m_sysPath = ( m_testRoot / "sys" ).string();
67
68 std::filesystem::create_directories( m_testRoot / MAGAOX_driverFIFORelPath );
69 std::filesystem::create_directories( std::filesystem::path( m_sysPath ) / m_configName );
70
71 m_stages.emplace_back( this );
72 m_stages.back().name( stageName );
73 m_stages.back().serial( "serial0" );
74
75 {
76 std::ofstream stateOut( std::filesystem::path( m_sysPath ) / m_configName / stageName );
77 stateOut << rawPos << '\n' << parked << '\n' << maxPos << '\n' << lastHomed << '\n';
78 }
79
80 if( appStartup() < 0 )
81 {
82 return -1;
83 }
84
85 if( createINDIFIFOS() < 0 )
86 {
87 return -1;
88 }
89
90 m_indiDriver = new indiDriver<MagAOXAppT>( this, m_configName, "0", "0" );
91
92 return ( m_indiDriver && m_indiDriver->good() ) ? 0 : -1;
93 }
94
95 /// Configure a stage entry for discovery tests.
96 int addConfiguredStage( const std::string &stageName, const std::string &serial, int deviceAddress = -1 )
97 {
98 m_stages.emplace_back( this );
99 m_stages.back().name( stageName );
100 m_stages.back().serial( serial );
101 m_stages.back().deviceAddress( deviceAddress );
102
103 const size_t idx = m_stages.size() - 1;
104
105 m_stageName.insert( { stageName, idx } );
106 m_stageSerial.insert( { serial, idx } );
107
108 return 0;
109 }
110
111 /// Load the parsed system-serial snapshot through the production discovery code.
112 int loadParsedStages( std::string serialResponse )
113 {
114 return loadStages( serialResponse );
115 }
116
117 /// Set the cached device address for a configured stage.
118 int setDeviceAddressFor( size_t stageIndex, int deviceAddress )
119 {
120 m_stages.at( stageIndex ).deviceAddress( deviceAddress );
121 return 0;
122 }
123
124 /// Get the cached device address for a configured stage.
125 int deviceAddressFor( size_t stageIndex ) const
126 {
127 return m_stages.at( stageIndex ).deviceAddress();
128 }
129
130 /// Drive the recoverable error handler under test.
131 int recoverTransportError( bool devicePresent )
132 {
133 return recoverFromError( devicePresent );
134 }
135
136 /// Set the FSM state for recovery tests.
137 int setAppState( stateCodes::stateCodeT newState )
138 {
139 state( newState );
140 return 0;
141 }
142
143 /// Get the FSM state for recovery tests.
144 stateCodes::stateCodeT appState() const
145 {
146 return state();
147 }
148
149 /// Read the value of a text or number element from a test property.
150 std::string propertyValue( const pcf::IndiProperty &property, const std::string &element ) const
151 {
152 return property[element].getValue();
153 }
154
155 /// Get the current-position property value for a stage.
156 std::string currPosValue( const std::string &stageName ) const
157 {
158 return propertyValue( m_indiP_curr_pos, stageName );
159 }
160
161 /// Get the target-position property value for a stage.
162 std::string tgtPosValue( const std::string &stageName ) const
163 {
164 return propertyValue( m_indiP_tgt_pos, stageName );
165 }
166
167 /// Get the parked-state property value for a stage.
168 std::string parkedValue( const std::string &stageName ) const
169 {
170 return propertyValue( m_indiP_parked, stageName );
171 }
172
173 /// Get the last-homed property value for a stage.
174 std::string lastHomedValue( const std::string &stageName ) const
175 {
176 return propertyValue( m_indiP_lastHomed, stageName );
177 }
178
179 /// Get the max-position property value for a stage.
180 std::string maxPosValue( const std::string &stageName ) const
181 {
182 return propertyValue( m_indiP_max_pos, stageName );
183 }
184
185 /// Get the current-state property value for a stage.
186 std::string currStateValue( const std::string &stageName ) const
187 {
188 return propertyValue( m_indiP_curr_state, stageName );
189 }
190
191 /// Get the warning-switch property value for a stage.
192 std::string warnValue( const std::string &stageName ) const
193 {
194 return propertyValue( m_indiP_warn, stageName );
195 }
196
197 /// Invoke the power-off handling under test.
198 int doOnPowerOff()
199 {
200 return onPowerOff();
201 }
202
203 ~zaberLowLevel_test() noexcept
204 {
205 std::error_code ec;
206
207 delete m_indiDriver;
208 m_indiDriver = nullptr;
209 std::filesystem::remove_all( m_testRoot, ec );
210 }
211
212 private:
213 std::filesystem::path m_testRoot; ///< Temporary directory backing the test FIFOs and state snapshot.
214};
215/// \endcond
216
217/// Verify zaberLowLevel callback validation and power-off snapshots preserve stage state.
218/**
219 * \ingroup zaberLowLevel_unit_test
220 */
221SCENARIO( "INDI Callbacks", "[zaberLowLevel]" )
222{
223 // clang-format off
224 #ifdef ZABERLOWLEVEL_TEST_DOXYGEN_REF
225 zaberLowLevel::newCallBack_m_indiP_tgt_pos( pcf::IndiProperty() );
226 zaberLowLevel::newCallBack_m_indiP_req_home( pcf::IndiProperty() );
228 #endif
229 // clang-format on
230
238}
239
240SCENARIO( "Power-off INDI snapshot retains stage state", "[zaberLowLevel]" )
241{
242 zaberLowLevel_test zllt( "zlltest" );
243
244 REQUIRE( zllt.setupPowerOffSnapshot( "stageA", 12345, true, 54321, 77 ) == 0 );
245
246 REQUIRE( zllt.doOnPowerOff() == 0 );
247
248 REQUIRE( zllt.currPosValue( "stageA" ) == "12345" );
249 REQUIRE( zllt.tgtPosValue( "stageA" ) == "12345" );
250 REQUIRE( zllt.parkedValue( "stageA" ) == "1" );
251 REQUIRE( zllt.lastHomedValue( "stageA" ) == "77" );
252 REQUIRE( zllt.maxPosValue( "stageA" ) == "54321" );
253 REQUIRE( zllt.currStateValue( "stageA" ) == "POWEROFF" );
254 REQUIRE( zllt.warnValue( "stageA" ) == "Off" );
255}
256
257/// Verify discovery clears stale addresses and reports missing configured stages safely.
258/**
259 * \ingroup zaberLowLevel_unit_test
260 */
261SCENARIO( "Stage discovery resets stale device addresses", "[zaberLowLevel]" )
262{
263 // clang-format off
264 #ifdef ZABERLOWLEVEL_TEST_DOXYGEN_REF
265 zaberLowLevel::loadStages( std::declval<std::string &>() );
266 #endif
267 // clang-format on
268
269 zaberLowLevel_test zllt( "zlltest" );
270
271 REQUIRE( zllt.addConfiguredStage( "stagebs", "49820", 1 ) == 0 );
272 REQUIRE( zllt.addConfiguredStage( "stageirf", "49821", 2 ) == 0 );
273
274 std::string serialResponse = "@01 0 OK IDLE WR 49820\n";
275
276 REQUIRE( zllt.loadParsedStages( serialResponse ) == ZC_CONNECTED );
277 REQUIRE( zllt.deviceAddressFor( 0 ) == 1 );
278 REQUIRE( zllt.deviceAddressFor( 1 ) < 1 );
279}
280
281/// Verify a later discovery pass can find a stage that was missing initially.
282/**
283 * \ingroup zaberLowLevel_unit_test
284 */
285SCENARIO( "Stage discovery can find devices that appear later", "[zaberLowLevel]" )
286{
287 zaberLowLevel_test zllt( "zlltest_rediscover" );
288
289 REQUIRE( zllt.addConfiguredStage( "stagebs", "49820" ) == 0 );
290 REQUIRE( zllt.addConfiguredStage( "stageirf", "49821" ) == 0 );
291
292 std::string serialResponse1 = "@01 0 OK IDLE WR 49820\n";
293 std::string serialResponse2 = "@01 0 OK IDLE WR 49820\n@02 0 OK IDLE WR 49821\n";
294
295 REQUIRE( zllt.loadParsedStages( serialResponse1 ) == ZC_CONNECTED );
296 REQUIRE( zllt.deviceAddressFor( 0 ) == 1 );
297 REQUIRE( zllt.deviceAddressFor( 1 ) < 1 );
298
299 REQUIRE( zllt.loadParsedStages( serialResponse2 ) == ZC_CONNECTED );
300 REQUIRE( zllt.deviceAddressFor( 0 ) == 1 );
301 REQUIRE( zllt.deviceAddressFor( 1 ) == 2 );
302}
303
304/// Verify communication failures drop the app back into reconnectable states.
305/**
306 * \ingroup zaberLowLevel_unit_test
307 */
308SCENARIO( "Recoverable transport errors transition to reconnect states", "[zaberLowLevel]" )
309{
310 // clang-format off
311 #ifdef ZABERLOWLEVEL_TEST_DOXYGEN_REF
314 #endif
315 // clang-format on
316
317 SECTION( "A present tty returns the app to NOTCONNECTED" )
318 {
319 zaberLowLevel_test zllt( "zlltest_present" );
320
321 REQUIRE( zllt.setupPowerOffSnapshot( "stageA", 12345, true, 54321, 77 ) == 0 );
322 REQUIRE( zllt.setDeviceAddressFor( 0, 1 ) == 0 );
323 REQUIRE( zllt.setAppState( stateCodes::ERROR ) == 0 );
324
325 REQUIRE( zllt.recoverTransportError( true ) == 0 );
326 REQUIRE( zllt.appState() == stateCodes::NOTCONNECTED );
327 REQUIRE( zllt.currStateValue( "stageA" ) == "NOTCONNECTED" );
328 }
329
330 SECTION( "A missing tty returns the app to NODEVICE" )
331 {
332 zaberLowLevel_test zllt( "zlltest_missing" );
333
334 REQUIRE( zllt.setupPowerOffSnapshot( "stageA", 12345, true, 54321, 77 ) == 0 );
335 REQUIRE( zllt.setDeviceAddressFor( 0, 1 ) == 0 );
336 REQUIRE( zllt.setAppState( stateCodes::ERROR ) == 0 );
337
338 REQUIRE( zllt.recoverTransportError( false ) == 0 );
339 REQUIRE( zllt.appState() == stateCodes::NODEVICE );
340 REQUIRE( zllt.currStateValue( "stageA" ) == "NODEVICE" );
341 }
342}
343
344} // namespace zaberLowLevelTest
345
346} // namespace libXWCTest
The low-level ASCII-protocol Zaber controller.
int recoverFromError(bool devicePresent)
Recover from an ASCII-transport error without terminating the app.
virtual int onPowerOff()
Handle the transition into the powered-off state.
int resetConnection()
Reset the active ASCII connection bookkeeping.
int loadStages(std::string &serialRes)
Apply a parsed system.serial snapshot to the configured stages.
#define MAGAOX_driverFIFORelPath
The relative path to the INDI driver FIFOs.
Definition paths.hpp:85
#define XWCTEST_INDI_NEW_CALLBACK(testclass, propname)
Catch-2 tests for whether a NEW callback properly validates the input property properly.
SCENARIO("INDI Callbacks", "[zaberLowLevel]")
Verify zaberLowLevel callback validation and power-off snapshots preserve stage state.
Namespace for all libXWC tests.
@ 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.
int16_t stateCodeT
The type of the state code.
#define XWCTEST_SETUP_INDI_NEW_PROP(propname)
#define ZC_CONNECTED