API
 
Loading...
Searching...
No Matches
streamWriter_test.cpp
Go to the documentation of this file.
1/** \file streamWriter_test.cpp
2 * \brief Catch2 tests for the streamWriter app.
3 * \author Jared R. Males (jaredmales@gmail.com)
4 *
5 * \ingroup streamWriter_files
6 */
7
8#include "../../../tests/testXWC.hpp"
9
10#include <cstdio>
11
12#include <xrif/xrif.h>
13
14/// \cond DOXYGEN_SUPPRESS_TEST_HARNESS
15namespace streamWriterFaultInject
16{
17
18/// Fault behavior for wrapped XRIF calls.
19enum class xrifFaultMode
20{
21 none,
22 fail,
23 passThroughError
24};
25
26/// Fault specification for a wrapped XRIF call.
27struct xrifFault
28{
29 xrifFaultMode mode{ xrifFaultMode::none }; ///< Current fault behavior.
30 int triggerCall{ 0 }; ///< 1-based call to fault, or `-1` for every call.
31 xrif_error_t error{ XRIF_ERROR_BADARG }; ///< Error code returned when the fault triggers.
32 int callCount{ 0 }; ///< Number of wrapped calls observed so far.
33};
34
35/// Fault specification for wrapped `fwrite()` calls.
36struct fwriteFault
37{
38 bool enabled{ false }; ///< Whether the wrapper should short-write.
39 int triggerCall{ 0 }; ///< 1-based `fwrite()` call to short-write, or `-1` for every call.
40 size_t resultNmemb{ 0 }; ///< Number of items reported as written when the fault triggers.
41 int callCount{ 0 }; ///< Number of wrapped calls observed so far.
42};
43
44/// Aggregate fault-injection state for `streamWriter` XRIF and file-write tests.
45struct state
46{
47 xrifFault configure; ///< Fault for `xrif_configure()`.
48 xrifFault setSize; ///< Fault for `xrif_set_size()`.
49 xrifFault allocateRaw; ///< Fault for `xrif_allocate_raw()`.
50 xrifFault allocateReordered; ///< Fault for `xrif_allocate_reordered()`.
51 xrifFault setLz4; ///< Fault for `xrif_set_lz4_acceleration()`.
52 xrifFault encode; ///< Fault for `xrif_encode()`.
53 xrifFault writeHeader; ///< Fault for `xrif_write_header()`.
54 fwriteFault write; ///< Fault for `fwrite()`.
55};
56
57/// Access the current process-local fault-injection state.
58state &faults();
59
60/// Restore all wrapped calls to their pass-through behavior.
61void reset();
62
63/// Return `true` when the XRIF fault should trigger for this call.
64bool shouldFault( xrifFault &fault );
65
66/// Return `true` when the `fwrite()` fault should trigger for this call.
67bool shouldFault( fwriteFault &fault );
68
69/// Scope helper that automatically resets fault-injection state on entry and exit.
70struct resetScope
71{
72 /// Reset all fault-injection state on construction.
73 resetScope()
74 {
75 reset();
76 }
77
78 /// Reset all fault-injection state on destruction.
79 ~resetScope()
80 {
81 reset();
82 }
83};
84
85} // namespace streamWriterFaultInject
86
87xrif_error_t
88streamWriter_test_xrif_configure( xrif_t handle, int difference_method, int reorder_method, int compress_method );
89xrif_error_t streamWriter_test_xrif_set_size(
90 xrif_t handle, xrif_dimension_t w, xrif_dimension_t h, xrif_dimension_t d, xrif_dimension_t f, xrif_typecode_t c );
91xrif_error_t streamWriter_test_xrif_allocate_raw( xrif_t handle );
92xrif_error_t streamWriter_test_xrif_allocate_reordered( xrif_t handle );
93xrif_error_t streamWriter_test_xrif_set_lz4_acceleration( xrif_t handle, int32_t lz4_accel );
94xrif_error_t streamWriter_test_xrif_encode( xrif_t handle );
95xrif_error_t streamWriter_test_xrif_write_header( char *header, xrif_t handle );
96size_t streamWriter_test_fwrite( const void *ptr, size_t size, size_t nmemb, FILE *stream );
97/// \endcond
98
99#define xrif_configure streamWriter_test_xrif_configure
100#define xrif_set_size streamWriter_test_xrif_set_size
101#define xrif_allocate_raw streamWriter_test_xrif_allocate_raw
102#define xrif_allocate_reordered streamWriter_test_xrif_allocate_reordered
103#define xrif_set_lz4_acceleration streamWriter_test_xrif_set_lz4_acceleration
104#define xrif_encode streamWriter_test_xrif_encode
105#define xrif_write_header streamWriter_test_xrif_write_header
106#define fwrite streamWriter_test_fwrite
107#define protected public
108#include "../streamWriter.hpp"
109#undef protected
110#undef fwrite
111#undef xrif_write_header
112#undef xrif_encode
113#undef xrif_set_lz4_acceleration
114#undef xrif_allocate_reordered
115#undef xrif_allocate_raw
116#undef xrif_set_size
117#undef xrif_configure
118
119#include "../../../tests/testMacrosINDI.hpp"
120
121#include <algorithm>
122#include <filesystem>
123#include <fstream>
124
125/// \cond DOXYGEN_SUPPRESS_TEST_HARNESS
126namespace streamWriterFaultInject
127{
128
129state &faults()
130{
131 static state s_faults;
132 return s_faults;
133}
134
135void reset()
136{
137 faults() = state{};
138}
139
140bool shouldFault( xrifFault &fault )
141{
142 ++fault.callCount;
143 return fault.mode != xrifFaultMode::none && ( fault.triggerCall == -1 || fault.callCount == fault.triggerCall );
144}
145
146bool shouldFault( fwriteFault &fault )
147{
148 ++fault.callCount;
149 return fault.enabled && ( fault.triggerCall == -1 || fault.callCount == fault.triggerCall );
150}
151
152} // namespace streamWriterFaultInject
153
154xrif_error_t
155streamWriter_test_xrif_configure( xrif_t handle, int difference_method, int reorder_method, int compress_method )
156{
157 auto &fault = streamWriterFaultInject::faults().configure;
158
159 if( streamWriterFaultInject::shouldFault( fault ) )
160 {
161 if( fault.mode == streamWriterFaultInject::xrifFaultMode::passThroughError )
162 {
163 xrif_error_t rv = ::xrif_configure( handle, difference_method, reorder_method, compress_method );
164 if( rv != XRIF_NOERROR )
165 {
166 return rv;
167 }
168 }
169
170 return fault.error;
171 }
172
173 return ::xrif_configure( handle, difference_method, reorder_method, compress_method );
174}
175
176xrif_error_t streamWriter_test_xrif_set_size(
177 xrif_t handle, xrif_dimension_t w, xrif_dimension_t h, xrif_dimension_t d, xrif_dimension_t f, xrif_typecode_t c )
178{
179 auto &fault = streamWriterFaultInject::faults().setSize;
180
181 if( streamWriterFaultInject::shouldFault( fault ) )
182 {
183 if( fault.mode == streamWriterFaultInject::xrifFaultMode::passThroughError )
184 {
185 xrif_error_t rv = ::xrif_set_size( handle, w, h, d, f, c );
186 if( rv != XRIF_NOERROR )
187 {
188 return rv;
189 }
190 }
191
192 return fault.error;
193 }
194
195 return ::xrif_set_size( handle, w, h, d, f, c );
196}
197
198xrif_error_t streamWriter_test_xrif_allocate_raw( xrif_t handle )
199{
200 auto &fault = streamWriterFaultInject::faults().allocateRaw;
201
202 if( streamWriterFaultInject::shouldFault( fault ) )
203 {
204 if( fault.mode == streamWriterFaultInject::xrifFaultMode::passThroughError )
205 {
206 xrif_error_t rv = ::xrif_allocate_raw( handle );
207 if( rv != XRIF_NOERROR )
208 {
209 return rv;
210 }
211 }
212
213 return fault.error;
214 }
215
216 return ::xrif_allocate_raw( handle );
217}
218
219xrif_error_t streamWriter_test_xrif_allocate_reordered( xrif_t handle )
220{
221 auto &fault = streamWriterFaultInject::faults().allocateReordered;
222
223 if( streamWriterFaultInject::shouldFault( fault ) )
224 {
225 if( fault.mode == streamWriterFaultInject::xrifFaultMode::passThroughError )
226 {
227 xrif_error_t rv = ::xrif_allocate_reordered( handle );
228 if( rv != XRIF_NOERROR )
229 {
230 return rv;
231 }
232 }
233
234 return fault.error;
235 }
236
237 return ::xrif_allocate_reordered( handle );
238}
239
240xrif_error_t streamWriter_test_xrif_set_lz4_acceleration( xrif_t handle, int32_t lz4_accel )
241{
242 auto &fault = streamWriterFaultInject::faults().setLz4;
243
244 if( streamWriterFaultInject::shouldFault( fault ) )
245 {
246 if( fault.mode == streamWriterFaultInject::xrifFaultMode::passThroughError )
247 {
248 xrif_error_t rv = ::xrif_set_lz4_acceleration( handle, lz4_accel );
249 if( rv != XRIF_NOERROR )
250 {
251 return rv;
252 }
253 }
254
255 return fault.error;
256 }
257
258 return ::xrif_set_lz4_acceleration( handle, lz4_accel );
259}
260
261xrif_error_t streamWriter_test_xrif_encode( xrif_t handle )
262{
263 auto &fault = streamWriterFaultInject::faults().encode;
264
265 if( streamWriterFaultInject::shouldFault( fault ) )
266 {
267 if( fault.mode == streamWriterFaultInject::xrifFaultMode::passThroughError )
268 {
269 xrif_error_t rv = ::xrif_encode( handle );
270 if( rv != XRIF_NOERROR )
271 {
272 return rv;
273 }
274 }
275
276 return fault.error;
277 }
278
279 return ::xrif_encode( handle );
280}
281
282xrif_error_t streamWriter_test_xrif_write_header( char *header, xrif_t handle )
283{
284 auto &fault = streamWriterFaultInject::faults().writeHeader;
285
286 if( streamWriterFaultInject::shouldFault( fault ) )
287 {
288 if( fault.mode == streamWriterFaultInject::xrifFaultMode::passThroughError )
289 {
290 xrif_error_t rv = ::xrif_write_header( header, handle );
291 if( rv != XRIF_NOERROR )
292 {
293 return rv;
294 }
295 }
296
297 return fault.error;
298 }
299
300 return ::xrif_write_header( header, handle );
301}
302
303size_t streamWriter_test_fwrite( const void *ptr, size_t size, size_t nmemb, FILE *stream )
304{
305 auto &fault = streamWriterFaultInject::faults().write;
306
307 if( streamWriterFaultInject::shouldFault( fault ) )
308 {
309 return ::fwrite( ptr, size, std::min( nmemb, fault.resultNmemb ), stream );
310 }
311
312 return ::fwrite( ptr, size, nmemb, stream );
313}
314/// \endcond
315
316using namespace MagAOX::app;
317
318namespace libXWCTest
319{
320
321/** \addtogroup streamWriter_unit_test
322 * \brief Additional unit tests for the streamWriter application.
323 *
324 * \ingroup application_unit_test
325 */
326
327/// Namespace for `streamWriter` unit tests.
328/** \ingroup streamWriter_unit_test
329 */
330namespace streamWriterTest
331{
332
333/// \cond DOXYGEN_SUPPRESS_TEST_HARNESS
334struct streamWriter_test : public streamWriter
335{
336 streamWriter *m_sw;
337
338 /// Construct a test harness instance with the minimum app identity needed by the callbacks.
339 streamWriter_test( const std::string &device )
340 {
341 m_configName = device;
342 m_outName = device;
343
345 }
346};
347/// \endcond
348
349/// \cond DOXYGEN_SUPPRESS_TEST_HARNESS
350struct streamWriter_data_test
351{
352 streamWriter_test *m_sw;
353
354 /// Bind the helper view to a `streamWriter` test harness.
355 streamWriter_data_test( streamWriter_test *sw )
356 {
357 m_sw = sw;
358 }
359
360 /// Return the configured raw image output directory.
361 std::string rawimageDir()
362 {
363 return m_sw->m_rawimageDir;
364 }
365
366 /// Build a valid `writing` toggle request property with the requested switch state.
367 pcf::IndiProperty writingToggle( pcf::IndiElement::SwitchStateType state )
368 {
369 pcf::IndiProperty ip;
370 m_sw->createStandardIndiToggleSw( ip, "writing" );
371 ip["toggle"].setSwitchState( state );
372
373 return ip;
374 }
375
376 /// Allocate circular buffers for a synthetic stream with the requested geometry and chunking.
377 int setup_circbufs( int width, int height, int dataType, int circBuffLength, int writeChunkLength )
378 {
379 m_sw->m_typeSize = ImageStreamIO_typesize( dataType );
380
381 m_sw->m_maxCircBuffLength = circBuffLength;
382 m_sw->m_maxCircBuffSize = ( width * height * m_sw->m_typeSize * circBuffLength + 1 ) / 1048576.0;
383 m_sw->m_maxWriteChunkLength = writeChunkLength;
384
385 m_sw->m_width = width;
386 m_sw->m_height = height;
387 m_sw->m_dataType = dataType;
388
389 return m_sw->allocate_circbufs();
390 }
391
392 /// Allocate the XRIF encoder state after the circular buffers have been configured.
393 int setup_xrif()
394 {
395 m_sw->initialize_xrif();
396 return m_sw->allocate_xrif();
397 }
398
399 /// Fill the circular buffers with deterministic image and timing data for round-trip comparisons.
400 int fill_circbuf_uint16()
401 {
402 // fill in image data with increasing 256 bit vals.
403 for( size_t pp = 0; pp < m_sw->m_circBuffLength; ++pp )
404 {
405 uint16_t v = pp;
406 for( size_t rr = 0; rr < m_sw->m_width; ++rr )
407 {
408 for( size_t cc = 0; cc < m_sw->m_height; ++cc )
409 {
410 ( (uint16_t *)
411 m_sw->m_rawImageCircBuff )[pp * m_sw->m_width * m_sw->m_height + rr * m_sw->m_height + cc] =
412 v;
413 ++v;
414 }
415 }
416
417 // fitsFile<uint16_t> ff;
418 // ff.write("cb.fits", m_sw->m_rawImageCircBuff);
419
420 // Fill in timing values with unique vals.
421 uint64_t *curr_timing = m_sw->m_timingCircBuff + 5 * pp;
422 curr_timing[0] = pp; // image number
423 curr_timing[1] = pp + 1000; // atime sec
424 curr_timing[2] = pp + 2000; // atime nsec
425 curr_timing[3] = pp + m_sw->m_circBuffLength + 1000; // wtime sec
426 curr_timing[4] = pp + m_sw->m_circBuffLength + 2000; // wtime nsec
427 }
428 return 0;
429 }
430
431 /// Encode the requested frame window into an XRIF output file.
432 int write_frames( int start, int stop )
433 {
434 m_sw->m_rawimageDir = "/tmp";
435
436 m_sw->m_currSaveStart = start;
437 m_sw->m_currSaveStop = stop;
438 m_sw->m_currSaveStopFrameNo = stop;
439
440 m_sw->m_writing = WRITING;
441 return m_sw->doEncode();
442 }
443
444 /// Decode the saved image and timing archives and compare them against the requested frame window.
445 int comp_frames_uint16( size_t start, size_t stop )
446 {
447 std::cout << "Reading: " << m_sw->m_outFilePath << "\n";
448
449 xrif_t xrif = nullptr;
450 if( xrif_new( &xrif ) != XRIF_NOERROR )
451 {
452 std::cerr << "Error allocating XRIF image decoder.\n";
453 return -1;
454 }
455
456 xrif_t xrif_timing = nullptr;
457 if( xrif_new( &xrif_timing ) != XRIF_NOERROR )
458 {
459 std::cerr << "Error allocating XRIF timing decoder.\n";
460 xrif_delete( xrif );
461 return -1;
462 }
463
464 char header[XRIF_HEADER_SIZE];
465
466 FILE *fp_xrif = fopen( m_sw->m_outFilePath.c_str(), "rb" );
467 if( fp_xrif == nullptr )
468 {
469 std::cerr << "Error opening " << m_sw->m_outFilePath << " for readback.\n";
470 xrif_delete( xrif );
471 xrif_delete( xrif_timing );
472 return -1;
473 }
474
475 size_t nr = fread( header, 1, XRIF_HEADER_SIZE, fp_xrif );
476
477 if( nr != XRIF_HEADER_SIZE )
478 {
479 std::cerr << "Error reading header of " << m_sw->m_outFilePath << "\n";
480 fclose( fp_xrif );
481 xrif_delete( xrif );
482 xrif_delete( xrif_timing );
483 return -1;
484 }
485
486 uint32_t header_size;
487 xrif_read_header( xrif, &header_size, header );
488
489 int rv = 0;
490 if( xrif_width( xrif ) != m_sw->m_width )
491 {
492 std::cerr << "width mismatch\n";
493 rv = -1;
494 }
495
496 if( xrif_height( xrif ) != m_sw->m_height )
497 {
498 std::cerr << "height mismatch\n";
499 rv = -1;
500 }
501
502 if( xrif_depth( xrif ) != 1 )
503 {
504 std::cerr << "depth mismatch\n";
505 rv = -1;
506 }
507
508 if( xrif_frames( xrif ) != stop - start )
509 {
510 std::cerr << "frames mismatch\n";
511 rv = -1;
512 }
513
514 xrif_allocate( xrif );
515
516 nr = fread( xrif->raw_buffer, 1, xrif->compressed_size, fp_xrif );
517
518 if( nr != xrif->compressed_size )
519 {
520 std::cerr << "error reading compressed image buffer.\n";
521 fclose( fp_xrif );
522 xrif_delete( xrif );
523 xrif_delete( xrif_timing );
524 return -1;
525 }
526
527 if( xrif_decode( xrif ) != XRIF_NOERROR )
528 {
529 std::cerr << "error decoding compressed image buffer.\n";
530 fclose( fp_xrif );
531 xrif_delete( xrif );
532 xrif_delete( xrif_timing );
533 return -1;
534 }
535
536 size_t badpix = 0;
537
538 for( size_t n = 0; n < m_sw->m_width * m_sw->m_height * m_sw->m_typeSize * ( stop - start ); ++n )
539 {
540 if( m_sw->m_rawImageCircBuff[start * m_sw->m_width * m_sw->m_height * m_sw->m_typeSize + n] !=
541 xrif->raw_buffer[n] )
542 ++badpix;
543 }
544
545 if( badpix > 0 )
546 {
547 std::cerr << "Buffers don't match: " << badpix << " bad pixels.\n";
548 rv = -1;
549 }
550
551 char timing_header[XRIF_HEADER_SIZE];
552 nr = fread( timing_header, 1, XRIF_HEADER_SIZE, fp_xrif );
553 if( nr != XRIF_HEADER_SIZE )
554 {
555 std::cerr << "Error reading timing header of " << m_sw->m_outFilePath << "\n";
556 fclose( fp_xrif );
557 xrif_delete( xrif );
558 xrif_delete( xrif_timing );
559 return -1;
560 }
561
562 uint32_t timing_header_size;
563 xrif_read_header( xrif_timing, &timing_header_size, timing_header );
564
565 if( xrif_width( xrif_timing ) != 5 )
566 {
567 std::cerr << "timing width mismatch\n";
568 rv = -1;
569 }
570
571 if( xrif_height( xrif_timing ) != 1 )
572 {
573 std::cerr << "timing height mismatch\n";
574 rv = -1;
575 }
576
577 if( xrif_depth( xrif_timing ) != 1 )
578 {
579 std::cerr << "timing depth mismatch\n";
580 rv = -1;
581 }
582
583 if( xrif_frames( xrif_timing ) != stop - start )
584 {
585 std::cerr << "timing frame count mismatch\n";
586 rv = -1;
587 }
588
589 xrif_allocate( xrif_timing );
590
591 nr = fread( xrif_timing->raw_buffer, 1, xrif_timing->compressed_size, fp_xrif );
592 if( nr != xrif_timing->compressed_size )
593 {
594 std::cerr << "error reading compressed timing buffer.\n";
595 fclose( fp_xrif );
596 xrif_delete( xrif );
597 xrif_delete( xrif_timing );
598 return -1;
599 }
600
601 if( xrif_decode( xrif_timing ) != XRIF_NOERROR )
602 {
603 std::cerr << "error decoding compressed timing buffer.\n";
604 fclose( fp_xrif );
605 xrif_delete( xrif );
606 xrif_delete( xrif_timing );
607 return -1;
608 }
609
610 size_t badtiming = 0;
611 for( size_t n = 0; n < 5 * ( stop - start ); ++n )
612 {
613 if( m_sw->m_timingCircBuff[start * 5 + n] != reinterpret_cast<uint64_t *>( xrif_timing->raw_buffer )[n] )
614 {
615 ++badtiming;
616 }
617 }
618
619 if( badtiming > 0 )
620 {
621 std::cerr << "Timing buffers don't match: " << badtiming << " bad entries.\n";
622 rv = -1;
623 }
624
625 fclose( fp_xrif );
626 xrif_delete( xrif );
627 xrif_delete( xrif_timing );
628
629 return rv;
630 }
631};
632/// \endcond
633
634/// Verify the streamWriter INDI callback validator accepts only the expected property.
635/**
636 * \ingroup streamWriter_unit_test
637 */
638SCENARIO( "streamWriter INDI Callbacks", "[streamWriter]" )
639{
640 // clang-format off
641 #ifdef STREAMWRITER_TEST_DOXYGEN_REF
642 streamWriter::newCallBack_m_indiP_writing( pcf::IndiProperty() );
644 #endif
645 // clang-format on
646
648}
649
650/// Verify the streamWriter writing toggle transitions and stop-write flushes preserve the final queued frame.
651/**
652 * \ingroup streamWriter_unit_test
653 */
654TEST_CASE( "streamWriter writing toggle transitions and stopped writes", "[streamWriter]" )
655{
656 // clang-format off
657 #ifdef STREAMWRITER_TEST_DOXYGEN_REF
658 XWCTEST_DOXYGEN_REF( streamWriter::newCallBack_m_indiP_writing( pcf::IndiProperty() ) );
660 #endif
661 // clang-format on
662
663 SECTION( "toggle requests only change state for valid start and stop transitions" )
664 {
665 streamWriter_test sw( "testdev" );
666 streamWriter_data_test sw_test( &sw );
667
668 pcf::IndiProperty ipOn = sw_test.writingToggle( pcf::IndiElement::On );
669 pcf::IndiProperty ipOff = sw_test.writingToggle( pcf::IndiElement::Off );
670
671 REQUIRE( sw.m_writing == NOT_WRITING );
672 REQUIRE( sw.newCallBack_m_indiP_writing( ipOn ) == 0 );
673 REQUIRE( sw.m_writing == START_WRITING );
674
675 REQUIRE( sw.newCallBack_m_indiP_writing( ipOn ) == 0 );
676 REQUIRE( sw.m_writing == START_WRITING );
677
678 REQUIRE( sw.newCallBack_m_indiP_writing( ipOff ) == 0 );
679 REQUIRE( sw.m_writing == STOP_WRITING );
680
681 REQUIRE( sw.newCallBack_m_indiP_writing( ipOn ) == 0 );
682 REQUIRE( sw.m_writing == STOP_WRITING );
683
684 sw.m_writing = NOT_WRITING;
685
686 REQUIRE( sw.newCallBack_m_indiP_writing( ipOff ) == 0 );
687 REQUIRE( sw.m_writing == NOT_WRITING );
688 }
689
690 SECTION( "stopping without queued frames settles back to NOT_WRITING" )
691 {
692 streamWriter_test sw( "testdev" );
693 streamWriter_data_test sw_test( &sw );
694
695 pcf::IndiProperty ipOn = sw_test.writingToggle( pcf::IndiElement::On );
696 pcf::IndiProperty ipOff = sw_test.writingToggle( pcf::IndiElement::Off );
697
698 REQUIRE( sw_test.setup_circbufs( 16, 16, XRIF_TYPECODE_UINT16, 8, 4 ) == 0 );
699 REQUIRE( sw_test.setup_xrif() == 0 );
700
701 REQUIRE( sw.newCallBack_m_indiP_writing( ipOn ) == 0 );
702 REQUIRE( sw.m_writing == START_WRITING );
703
704 REQUIRE( sw.newCallBack_m_indiP_writing( ipOff ) == 0 );
705 REQUIRE( sw.m_writing == STOP_WRITING );
706
707 sw.m_currSaveStart = 0;
708 sw.m_currSaveStop = 0;
709 sw.m_currSaveStopFrameNo = 17;
710
711 REQUIRE( sw.doEncode() == 0 );
712 REQUIRE( sw.m_writing == NOT_WRITING );
713 }
714
715 SECTION( "stopping with pending frames encodes through the final queued frame" )
716 {
717 streamWriter_test sw( "testdev" );
718 streamWriter_data_test sw_test( &sw );
719
720 pcf::IndiProperty ipOff = sw_test.writingToggle( pcf::IndiElement::Off );
721
722 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, 10, 5 ) == 0 );
723 REQUIRE( sw_test.setup_xrif() == 0 );
724 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
725
726 sw.m_writing = WRITING;
727 REQUIRE( sw.newCallBack_m_indiP_writing( ipOff ) == 0 );
728 REQUIRE( sw.m_writing == STOP_WRITING );
729
730 sw.m_rawimageDir = "/tmp";
731 sw.m_currSaveStart = 5;
732 sw.m_currSaveStop = 8;
733 sw.m_currSaveStopFrameNo = 8;
734
735 REQUIRE( sw.doEncode() == 0 );
736 REQUIRE( sw.m_writing == NOT_WRITING );
737 REQUIRE( sw_test.comp_frames_uint16( 5, 8 ) == 0 );
738 }
739}
740
741/// Verify streamWriter encode/setup helpers cover allocation and write-failure edge cases.
742/**
743 * \ingroup streamWriter_unit_test
744 */
745TEST_CASE( "streamWriter allocation and encode edge cases", "[streamWriter]" )
746{
747 // clang-format off
748 #ifdef STREAMWRITER_TEST_DOXYGEN_REF
752 #endif
753 // clang-format on
754
755 SECTION( "allocate_circbufs rejects frames that cannot fit in the requested maximum buffer size" )
756 {
757 streamWriter_test sw( "testdev" );
758
759 sw.m_typeSize = ImageStreamIO_typesize( XRIF_TYPECODE_UINT16 );
760 sw.m_maxCircBuffLength = 1024;
761 sw.m_maxCircBuffSize = 1024.0;
762 sw.m_maxWriteChunkLength = 512;
763 sw.m_width = 16385;
764 sw.m_height = 16385;
765 sw.m_dataType = XRIF_TYPECODE_UINT16;
766
767 REQUIRE( sw.allocate_circbufs() < 0 );
768 }
769
770 SECTION( "uncompressed XRIF encoding still preserves the saved frames" )
771 {
772 streamWriter_test sw( "testdev" );
773 streamWriter_data_test sw_test( &sw );
774
775 sw.m_compress = false;
776
777 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, 10, 5 ) == 0 );
778 REQUIRE( sw_test.setup_xrif() == 0 );
779 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
780 REQUIRE( sw_test.write_frames( 0, 5 ) == 0 );
781 REQUIRE( sw_test.comp_frames_uint16( 0, 5 ) == 0 );
782 }
783
784 SECTION( "doEncode returns immediately when not writing" )
785 {
786 streamWriter_test sw( "testdev" );
787
788 sw.m_writing = NOT_WRITING;
789
790 REQUIRE( sw.doEncode() == 0 );
791 }
792
793 SECTION( "doEncode rejects an all-zero timestamp for the first saved frame" )
794 {
795 streamWriter_test sw( "testdev" );
796 streamWriter_data_test sw_test( &sw );
797
798 REQUIRE( sw_test.setup_circbufs( 16, 16, XRIF_TYPECODE_UINT16, 8, 4 ) == 0 );
799 REQUIRE( sw_test.setup_xrif() == 0 );
800 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
801
802 std::fill_n( sw.m_timingCircBuff, 5 * sw.m_circBuffLength, 0ULL );
803
804 sw.m_rawimageDir = "/tmp";
805 sw.m_currSaveStart = 0;
806 sw.m_currSaveStop = 1;
807 sw.m_currSaveStopFrameNo = 1;
808 sw.m_writing = WRITING;
809
810 REQUIRE( sw.doEncode() < 0 );
811 }
812
813 SECTION( "doEncode reports directory creation failures below a non-directory save root" )
814 {
815 streamWriter_test sw( "testdev" );
816 streamWriter_data_test sw_test( &sw );
817
818 const std::filesystem::path blocker = std::filesystem::path( "/tmp" ) / "streamWriter_encode_blocker";
819
820 std::filesystem::remove( blocker );
821 {
822 std::ofstream blockerOut( blocker.string() );
823 blockerOut << "block";
824 }
825
826 REQUIRE( sw_test.setup_circbufs( 16, 16, XRIF_TYPECODE_UINT16, 8, 4 ) == 0 );
827 REQUIRE( sw_test.setup_xrif() == 0 );
828 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
829
830 sw.m_rawimageDir = blocker.string();
831 sw.m_currSaveStart = 0;
832 sw.m_currSaveStop = 1;
833 sw.m_currSaveStopFrameNo = 1;
834 sw.m_writing = WRITING;
835
836 REQUIRE( sw.doEncode() < 0 );
837
838 std::filesystem::remove( blocker );
839 }
840
841 SECTION( "doEncode reports file open failures when the output name contains a path separator" )
842 {
843 streamWriter_test sw( "testdev" );
844 streamWriter_data_test sw_test( &sw );
845
846 REQUIRE( sw_test.setup_circbufs( 16, 16, XRIF_TYPECODE_UINT16, 8, 4 ) == 0 );
847 REQUIRE( sw_test.setup_xrif() == 0 );
848 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
849
850 sw.m_rawimageDir = "/tmp";
851 sw.m_outName = "bad/name";
852 sw.m_currSaveStart = 0;
853 sw.m_currSaveStop = 1;
854 sw.m_currSaveStopFrameNo = 1;
855 sw.m_writing = WRITING;
856
857 REQUIRE( sw.doEncode() < 0 );
858 }
859}
860
861/// Verify injected XRIF and file-write faults exercise `streamWriter` warning and failure handling.
862/**
863 * \ingroup streamWriter_unit_test
864 */
865TEST_CASE( "streamWriter fault injection covers XRIF setup and write warnings", "[streamWriter]" )
866{
867 // clang-format off
868 #ifdef STREAMWRITER_TEST_DOXYGEN_REF
872 #endif
873 // clang-format on
874
875 SECTION( "allocate_xrif fails when image XRIF configuration faults" )
876 {
877 streamWriterFaultInject::resetScope faultScope;
878 streamWriter_test sw( "testdev" );
879 streamWriter_data_test sw_test( &sw );
880
881 REQUIRE( sw_test.setup_circbufs( 16, 16, XRIF_TYPECODE_UINT16, 8, 4 ) == 0 );
882 REQUIRE( sw.initialize_xrif() == 0 );
883
884 streamWriterFaultInject::reset();
885 streamWriterFaultInject::faults().configure.mode = streamWriterFaultInject::xrifFaultMode::fail;
886 streamWriterFaultInject::faults().configure.triggerCall = 1;
887
888 REQUIRE( sw.allocate_xrif() < 0 );
889 }
890
891 SECTION( "allocate_xrif fails when timing XRIF reordered allocation faults" )
892 {
893 streamWriterFaultInject::resetScope faultScope;
894 streamWriter_test sw( "testdev" );
895 streamWriter_data_test sw_test( &sw );
896
897 REQUIRE( sw_test.setup_circbufs( 16, 16, XRIF_TYPECODE_UINT16, 8, 4 ) == 0 );
898 REQUIRE( sw.initialize_xrif() == 0 );
899
900 streamWriterFaultInject::reset();
901 streamWriterFaultInject::faults().allocateReordered.mode = streamWriterFaultInject::xrifFaultMode::fail;
902 streamWriterFaultInject::faults().allocateReordered.triggerCall = 2;
903 streamWriterFaultInject::faults().allocateReordered.error = XRIF_ERROR_MALLOC;
904
905 REQUIRE( sw.allocate_xrif() < 0 );
906 }
907
908 SECTION( "doEncode still recovers frames when image XRIF size reports a warning after setup" )
909 {
910 streamWriterFaultInject::resetScope faultScope;
911 streamWriter_test sw( "testdev" );
912 streamWriter_data_test sw_test( &sw );
913
914 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, 10, 5 ) == 0 );
915 REQUIRE( sw_test.setup_xrif() == 0 );
916 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
917
918 streamWriterFaultInject::faults().setSize.mode = streamWriterFaultInject::xrifFaultMode::passThroughError;
919 streamWriterFaultInject::faults().setSize.triggerCall = 1;
920 streamWriterFaultInject::faults().setSize.error = XRIF_ERROR_INVALID_SIZE;
921
922 REQUIRE( sw_test.write_frames( 0, 5 ) == 0 );
923 REQUIRE( sw_test.comp_frames_uint16( 0, 5 ) == 0 );
924 }
925
926 SECTION( "doEncode still recovers frames when timing LZ4 setup reports a warning" )
927 {
928 streamWriterFaultInject::resetScope faultScope;
929 streamWriter_test sw( "testdev" );
930 streamWriter_data_test sw_test( &sw );
931
932 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, 10, 5 ) == 0 );
933 REQUIRE( sw_test.setup_xrif() == 0 );
934 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
935
936 streamWriterFaultInject::faults().setLz4.mode = streamWriterFaultInject::xrifFaultMode::fail;
937 streamWriterFaultInject::faults().setLz4.triggerCall = 2;
938 streamWriterFaultInject::faults().setLz4.error = XRIF_ERROR_BADARG;
939
940 REQUIRE( sw_test.write_frames( 5, 10 ) == 0 );
941 REQUIRE( sw_test.comp_frames_uint16( 5, 10 ) == 0 );
942 }
943
944 SECTION( "doEncode still recovers frames when timing encode reports an alert after encoding" )
945 {
946 streamWriterFaultInject::resetScope faultScope;
947 streamWriter_test sw( "testdev" );
948 streamWriter_data_test sw_test( &sw );
949
950 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, 10, 5 ) == 0 );
951 REQUIRE( sw_test.setup_xrif() == 0 );
952 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
953
954 streamWriterFaultInject::faults().encode.mode = streamWriterFaultInject::xrifFaultMode::passThroughError;
955 streamWriterFaultInject::faults().encode.triggerCall = 2;
956 streamWriterFaultInject::faults().encode.error = XRIF_ERROR_NOTIMPL;
957
958 REQUIRE( sw_test.write_frames( 2, 5 ) == 0 );
959 REQUIRE( sw_test.comp_frames_uint16( 2, 5 ) == 0 );
960 }
961
962 SECTION( "doEncode still recovers frames when timing header generation reports an alert" )
963 {
964 streamWriterFaultInject::resetScope faultScope;
965 streamWriter_test sw( "testdev" );
966 streamWriter_data_test sw_test( &sw );
967
968 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, 10, 5 ) == 0 );
969 REQUIRE( sw_test.setup_xrif() == 0 );
970 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
971
972 streamWriterFaultInject::faults().writeHeader.mode = streamWriterFaultInject::xrifFaultMode::passThroughError;
973 streamWriterFaultInject::faults().writeHeader.triggerCall = 2;
974 streamWriterFaultInject::faults().writeHeader.error = XRIF_ERROR_NULLPTR;
975
976 REQUIRE( sw_test.write_frames( 5, 8 ) == 0 );
977 REQUIRE( sw_test.comp_frames_uint16( 5, 8 ) == 0 );
978 }
979
980 SECTION( "doEncode returns success but leaves a truncated archive after a short image-data write" )
981 {
982 streamWriterFaultInject::resetScope faultScope;
983 streamWriter_test sw( "testdev" );
984 streamWriter_data_test sw_test( &sw );
985
986 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, 10, 5 ) == 0 );
987 REQUIRE( sw_test.setup_xrif() == 0 );
988 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
989
990 streamWriterFaultInject::faults().write.enabled = true;
991 streamWriterFaultInject::faults().write.triggerCall = 2;
992 streamWriterFaultInject::faults().write.resultNmemb = sw.m_xrif->compressed_size / 2;
993
994 REQUIRE( sw_test.write_frames( 0, 5 ) == 0 );
995 REQUIRE( sw_test.comp_frames_uint16( 0, 5 ) < 0 );
996 }
997
998 SECTION( "doEncode returns success but leaves a truncated archive after a short timing-data write" )
999 {
1000 streamWriterFaultInject::resetScope faultScope;
1001 streamWriter_test sw( "testdev" );
1002 streamWriter_data_test sw_test( &sw );
1003
1004 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, 10, 5 ) == 0 );
1005 REQUIRE( sw_test.setup_xrif() == 0 );
1006 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
1007
1008 streamWriterFaultInject::faults().write.enabled = true;
1009 streamWriterFaultInject::faults().write.triggerCall = 4;
1010 streamWriterFaultInject::faults().write.resultNmemb =
1011 std::max<size_t>( 1, sw.m_xrif_timing->compressed_size / 2 );
1012
1013 REQUIRE( sw_test.write_frames( 7, 8 ) == 0 );
1014 REQUIRE( sw_test.comp_frames_uint16( 7, 8 ) < 0 );
1015 }
1016}
1017
1018/// Verify the streamWriter test harness exposes the expected default configuration state.
1019/**
1020 * \ingroup streamWriter_unit_test
1021 */
1022SCENARIO( "streamWriter Configuration", "[streamWriter]" )
1023{
1024 GIVEN( "A default constructed streamWriter" )
1025 {
1026 streamWriter_test sw( "testdev" );
1027 streamWriter_data_test sw_test( &sw );
1028
1029 WHEN( "default configurations" )
1030 {
1031 REQUIRE( sw_test.rawimageDir() == "" );
1032 }
1033 }
1034}
1035
1036/// Verify streamWriter encodes raw image buffers into XRIF archives without corrupting frame data.
1037/**
1038 * \ingroup streamWriter_unit_test
1039 */
1040SCENARIO( "streamWriter encoding data", "[streamWriter]" )
1041{
1042 GIVEN( "A default constructed streamWriter and a 120x120 uint16 stream" )
1043 {
1044 streamWriter_test sw( "testdev" );
1045 streamWriter_data_test sw_test( &sw );
1046
1047 WHEN( "writing full 1st chunk" )
1048 {
1049 int circBuffLength = 10;
1050 int writeChunkLength = 5;
1051 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, circBuffLength, writeChunkLength ) == 0 );
1052 REQUIRE( sw_test.setup_xrif() == 0 );
1053
1054 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
1055
1056 REQUIRE( sw_test.write_frames( 0, 5 ) == 0 );
1057
1058 REQUIRE( sw_test.comp_frames_uint16( 0, 5 ) == 0 );
1059 }
1060
1061 WHEN( "writing full 2nd chunk" )
1062 {
1063 int circBuffLength = 10;
1064 int writeChunkLength = 5;
1065 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, circBuffLength, writeChunkLength ) == 0 );
1066 REQUIRE( sw_test.setup_xrif() == 0 );
1067 // REQUIRE( sw_test.setup_fname() == 0 );
1068
1069 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
1070
1071 REQUIRE( sw_test.write_frames( 5, 10 ) == 0 );
1072
1073 REQUIRE( sw_test.comp_frames_uint16( 5, 10 ) == 0 );
1074 }
1075
1076 WHEN( "writing partial 1st chunk" )
1077 {
1078 int circBuffLength = 10;
1079 int writeChunkLength = 5;
1080 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, circBuffLength, writeChunkLength ) == 0 );
1081 REQUIRE( sw_test.setup_xrif() == 0 );
1082 // REQUIRE( sw_test.setup_fname() == 0 );
1083
1084 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
1085
1086 REQUIRE( sw_test.write_frames( 2, 5 ) == 0 );
1087
1088 REQUIRE( sw_test.comp_frames_uint16( 2, 5 ) == 0 );
1089 }
1090
1091 WHEN( "writing partial 2nd chunk" )
1092 {
1093 int circBuffLength = 10;
1094 int writeChunkLength = 5;
1095 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, circBuffLength, writeChunkLength ) == 0 );
1096 REQUIRE( sw_test.setup_xrif() == 0 );
1097 // REQUIRE( sw_test.setup_fname() == 0 );
1098
1099 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
1100
1101 REQUIRE( sw_test.write_frames( 5, 8 ) == 0 );
1102
1103 REQUIRE( sw_test.comp_frames_uint16( 5, 8 ) == 0 );
1104 }
1105
1106 WHEN( "writing a single middle frame" )
1107 {
1108 int circBuffLength = 10;
1109 int writeChunkLength = 5;
1110 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, circBuffLength, writeChunkLength ) == 0 );
1111 REQUIRE( sw_test.setup_xrif() == 0 );
1112
1113 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
1114
1115 REQUIRE( sw_test.write_frames( 4, 5 ) == 0 );
1116
1117 REQUIRE( sw_test.comp_frames_uint16( 4, 5 ) == 0 );
1118 }
1119
1120 WHEN( "writing a shorter follow-up save after a longer save" )
1121 {
1122 int circBuffLength = 10;
1123 int writeChunkLength = 5;
1124 REQUIRE( sw_test.setup_circbufs( 120, 120, XRIF_TYPECODE_UINT16, circBuffLength, writeChunkLength ) == 0 );
1125 REQUIRE( sw_test.setup_xrif() == 0 );
1126
1127 REQUIRE( sw_test.fill_circbuf_uint16() == 0 );
1128
1129 REQUIRE( sw_test.write_frames( 0, 5 ) == 0 );
1130 REQUIRE( sw_test.comp_frames_uint16( 0, 5 ) == 0 );
1131
1132 REQUIRE( sw_test.write_frames( 7, 8 ) == 0 );
1133 REQUIRE( sw_test.comp_frames_uint16( 7, 8 ) == 0 );
1134 }
1135 }
1136}
1137
1138} // namespace streamWriterTest
1139
1140} // namespace libXWCTest
int initialize_xrif()
Initialize the xrif system.
int allocate_circbufs()
Worker function to allocate the circular buffers.
int allocate_xrif()
Worker function to configure and allocate the xrif handles.
int doEncode()
Function called when semaphore is raised to do the encode and write.
SCENARIO("streamWriter INDI Callbacks", "[streamWriter]")
Verify the streamWriter INDI callback validator accepts only the expected property.
TEST_CASE("streamWriter configuration loads defaults and overrides", "[streamWriter]")
Verify setupConfig() and loadConfig() preserve defaults, overrides, and clamp invalid accelerations.
#define XWCTEST_INDI_NEW_CALLBACK(testclass, propname)
Catch-2 tests for whether a NEW callback properly validates the input property properly.
#define XWCTEST_DOXYGEN_REF(fxn)
This inserts an unused call to a function signature to make doxygen make the link.
Definition testXWC.hpp:18
@ none
Don't publish.
Namespace for all libXWC tests.
#define NOT_WRITING
#define STOP_WRITING
#define WRITING
#define START_WRITING
#define xrif_encode
#define xrif_configure
#define xrif_set_size
#define xrif_allocate_reordered
#define xrif_write_header
#define xrif_set_lz4_acceleration
#define xrif_allocate_raw
#define XWCTEST_SETUP_INDI_NEW_PROP(propname)