mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
fitsHeaderCard_test.cpp
Go to the documentation of this file.
1/** \file fitsHeaderCard_test.cpp
2 * \brief Tests FITS header-card parsing and formatting.
3 */
4#include "../../../catch2/catch.hpp"
5
6#include <filesystem>
7#include <unistd.h>
8
9#define MX_NO_ERROR_REPORTS
10
12using namespace mx::fits;
13
14namespace mx
15{
16namespace unitTest
17{
18namespace fitsTest
19{
20namespace fitsHeaderCardTest
21{
22
23/** \cond */
24template <class verboseT = mx::verbose::d>
25class testHeaderCard : public fitsHeaderCard<verboseT>
26{
27 public:
28 using fitsHeaderCard<verboseT>::fitsHeaderCard;
29
30 void forceTypeWithValue( int type )
31 {
32 this->value( 1 );
33 this->m_type = type;
34 this->m_valueGood = true;
35 this->m_valueStrGood = false;
36 }
37};
38
39class temporaryFitsFile
40{
41 public:
42 temporaryFitsFile()
43 {
44 m_path = std::filesystem::temp_directory_path() /
45 ( "mxlib-fitsHeaderCard-" + std::to_string( static_cast<long long>( getpid() ) ) + ".fits" );
46 std::filesystem::remove( m_path );
47
48 int status = 0;
49 std::string createPath = "!" + m_path.string();
50 fits_create_file( &m_file, createPath.c_str(), &status );
51 if( status != 0 )
52 {
53 throw std::runtime_error( "could not create temporary FITS file" );
54 }
55
56 long axes[1] = { 1 };
57 fits_create_img( m_file, BYTE_IMG, 1, axes, &status );
58 if( status != 0 )
59 {
60 throw std::runtime_error( "could not create temporary FITS image" );
61 }
62 }
63
64 ~temporaryFitsFile()
65 {
66 if( m_file != nullptr )
67 {
68 int status = 0;
69 fits_close_file( m_file, &status );
70 }
71 std::error_code error;
72 std::filesystem::remove( m_path, error );
73 }
74
75 fitsfile *get()
76 {
77 return m_file;
78 }
79
80 private:
81 std::filesystem::path m_path;
82 fitsfile *m_file{ nullptr };
83};
84/** \endcond */
85
86/** \brief Verifies construction, copying, assignment, and scalar access for every supported numeric card type.
87 *
88 * \ingroup fitsHeaderCard_unit_tests
89 */
90TEST_CASE( "Round-tripping native FITS header card values", "[ioutils::fits::fitsHeaderCard]" )
91{
92 fitsHeaderCard<> empty;
93 REQUIRE( empty.keyword().empty() );
94 REQUIRE( empty.type() == fitsType<fitsUnknownType>() );
95 REQUIRE_FALSE( empty.valueGood() );
96 REQUIRE_FALSE( empty.valueStrGood() );
97
98 fitsHeaderCard<> character( "CHAR", static_cast<char>( 12 ), "char" );
99 fitsHeaderCard<> unsignedCharacter( "UCHAR", static_cast<unsigned char>( 13 ), "uchar" );
100 fitsHeaderCard<> shortInteger( "SHORT", static_cast<short>( 14 ), "short" );
101 fitsHeaderCard<> unsignedShort( "USHORT", static_cast<unsigned short>( 15 ), "ushort" );
102 fitsHeaderCard<> integer( "INT", 16, "int" );
103 fitsHeaderCard<> unsignedInteger( "UINT", 17U, "uint" );
104 fitsHeaderCard<> longInteger( "LONG", 18L, "long" );
105 fitsHeaderCard<> unsignedLong( "ULONG", 19UL, "ulong" );
106 fitsHeaderCard<> longLongInteger( "LONGLONG", 20LL, "long long" );
107 fitsHeaderCard<> unsignedLongLong( "ULONGLONG", 21ULL, "unsigned long long" );
108 fitsHeaderCard<> single( "FLOAT", 22.5F, "float" );
109 fitsHeaderCard<> real( "DOUBLE", 23.5, "double" );
110 fitsHeaderCard<> complexSingle( "COMPLEXF", std::complex<float>( 1.0F, -2.0F ), "complex float" );
111 fitsHeaderCard<> complexReal( "COMPLEXD", std::complex<double>( 3.0, -4.0 ), "complex double" );
112 fitsHeaderCard<> extended( "LONGDOUBLE", static_cast<long double>( 5.0 ), "long double" );
113 fitsHeaderCard<> complexExtended(
114 "COMPLEXLD",
115 std::complex<long double>( static_cast<long double>( 6.0 ), static_cast<long double>( -7.0 ) ),
116 "complex long double" );
117#ifdef HAS_QUAD
118 fitsHeaderCard<> quad( "QUAD", static_cast<__float128>( 8.0 ), "quad" );
119 fitsHeaderCard<> complexQuad(
120 "COMPLEXQ",
121 std::complex<__float128>( static_cast<__float128>( 9.0 ), static_cast<__float128>( -10.0 ) ),
122 "complex quad" );
123#endif
124
126 REQUIRE( character.Char( &error ) == 12 );
127 REQUIRE( error == mx::error_t::noerror );
128 REQUIRE( unsignedCharacter.UChar() == 13 );
129 REQUIRE( shortInteger.Short() == 14 );
130 REQUIRE( unsignedShort.UShort() == 15 );
131 REQUIRE( integer.Int() == 16 );
132 REQUIRE( unsignedInteger.UInt() == 17U );
133 REQUIRE( longInteger.Long() == 18L );
134 REQUIRE( unsignedLong.ULong() == 19UL );
135 REQUIRE( longLongInteger.LongLong() == 20LL );
136 REQUIRE( unsignedLongLong.ULongLong() == 21ULL );
137 REQUIRE( single.Float() == 22.5F );
138 REQUIRE( real.Double() == 23.5 );
139
140 REQUIRE( character.valueStr() == "12" );
141 REQUIRE( unsignedCharacter.valueStr() == "13" );
142 REQUIRE( shortInteger.valueStr() == "14" );
143 REQUIRE( unsignedShort.valueStr() == "15" );
144 REQUIRE( integer.valueStr() == "16" );
145 REQUIRE( unsignedInteger.valueStr() == "17" );
146 REQUIRE( longInteger.valueStr() == "18" );
147 REQUIRE( unsignedLong.valueStr() == "19" );
148 REQUIRE( longLongInteger.valueStr() == "20" );
149 REQUIRE( unsignedLongLong.valueStr() == "21" );
150 REQUIRE( single.valueStr() == "22.5" );
151 REQUIRE( real.valueStr() == "23.5" );
152 REQUIRE( complexSingle.valueStr() == "(1,-2)" );
153 REQUIRE( complexReal.valueStr() == "(3,-4)" );
154 REQUIRE( extended.valueGood() );
155 REQUIRE( complexExtended.valueGood() );
156#ifdef HAS_QUAD
157 REQUIRE( quad.valueGood() );
158 REQUIRE( complexQuad.valueGood() );
159#endif
160
161 fitsHeaderCard<> copied( real );
162 REQUIRE( copied.keyword() == "DOUBLE" );
163 REQUIRE( copied.Double() == 23.5 );
164 REQUIRE( copied.comment() == "double" );
165
166 fitsHeaderCard<> assigned( "OLD", 1, "old" );
167 assigned = unsignedLongLong;
168 REQUIRE( assigned.keyword() == "ULONGLONG" );
169 REQUIRE( assigned.ULongLong() == 21ULL );
170 REQUIRE( assigned.comment() == "unsigned long long" );
171 assigned = assigned;
172 REQUIRE( assigned.ULongLong() == 21ULL );
173
174 REQUIRE( assigned.keyword( "RENAMED" ) == mx::error_t::noerror );
175 REQUIRE( assigned.comment( "updated" ) == mx::error_t::noerror );
176 REQUIRE( assigned.keyword() == "RENAMED" );
177 REQUIRE( assigned.comment() == "updated" );
178}
179
180/** \brief Verifies parsing a stored FITS value string into every supported numeric scalar type.
181 *
182 * \ingroup fitsHeaderCard_unit_tests
183 */
184TEST_CASE( "Parsing numeric FITS header card strings", "[ioutils::fits::fitsHeaderCard]" )
185{
186 fitsHeaderCard<> character( "CHAR", "12", fitsType<char>() );
187 fitsHeaderCard<> unsignedCharacter( "UCHAR", "13", fitsType<unsigned char>() );
188 fitsHeaderCard<> shortInteger( "SHORT", "14", fitsType<short>() );
189 fitsHeaderCard<> unsignedShort( "USHORT", "15", fitsType<unsigned short>() );
190 fitsHeaderCard<> integer( "INT", "16", fitsType<int>() );
191 fitsHeaderCard<> unsignedInteger( "UINT", "17", fitsType<unsigned int>() );
192 fitsHeaderCard<> longInteger( "LONG", "18", fitsType<long>() );
193 fitsHeaderCard<> unsignedLong( "ULONG", "19", fitsType<unsigned long>() );
194 fitsHeaderCard<> longLongInteger( "LONGLONG", "20", fitsType<long long>() );
195 fitsHeaderCard<> unsignedLongLong( "ULONGLONG", "21", fitsType<unsigned long long>() );
196 fitsHeaderCard<> single( "FLOAT", "22.5", fitsType<float>() );
197 fitsHeaderCard<> real( "DOUBLE", "23.5", fitsType<double>() );
198
199 REQUIRE( character.Char() == 12 );
200 REQUIRE( unsignedCharacter.UChar() == 13 );
201 REQUIRE( shortInteger.Short() == 14 );
202 REQUIRE( unsignedShort.UShort() == 15 );
203 REQUIRE( integer.Int() == 16 );
204 REQUIRE( unsignedInteger.UInt() == 17U );
205 REQUIRE( longInteger.Long() == 18L );
206 REQUIRE( unsignedLong.ULong() == 19UL );
207 REQUIRE( longLongInteger.LongLong() == 20LL );
208 REQUIRE( unsignedLongLong.ULongLong() == 21ULL );
209 REQUIRE( single.Float() == 22.5F );
210 REQUIRE( real.Double() == 23.5 );
211
212 fitsHeaderCard<> invalid( "BAD", "not-a-number", fitsType<int>() );
214 REQUIRE( invalid.Int( &error ) == 0 );
215 REQUIRE( error != mx::error_t::noerror );
216 REQUIRE_FALSE( invalid.valueGood() );
217}
218
219/** \brief Verifies numeric fitsHeaderCard conversions between every supported scalar source and target type.
220 *
221 * \ingroup fitsHeaderCard_unit_tests
222 */
223TEST_CASE( "Converting numeric FITS header card types", "[ioutils::fits::fitsHeaderCard]" )
224{
225 fitsHeaderCard<> character( "CHAR", static_cast<char>( 7 ), "" );
226 fitsHeaderCard<> unsignedCharacter( "UCHAR", static_cast<unsigned char>( 7 ), "" );
227 fitsHeaderCard<> shortInteger( "SHORT", static_cast<short>( 7 ), "" );
228 fitsHeaderCard<> unsignedShort( "USHORT", static_cast<unsigned short>( 7 ), "" );
229 fitsHeaderCard<> integerSource( "INT", 7, "" );
230 fitsHeaderCard<> unsignedInteger( "UINT", 7U, "" );
231 fitsHeaderCard<> longInteger( "LONG", 7L, "" );
232 fitsHeaderCard<> unsignedLong( "ULONG", 7UL, "" );
233 fitsHeaderCard<> longLongInteger( "LONGLONG", 7LL, "" );
234 fitsHeaderCard<> unsignedLongLong( "ULONGLONG", 7ULL, "" );
235 fitsHeaderCard<> single( "FLOAT", 7.0F, "" );
236 fitsHeaderCard<> real( "DOUBLE", 7.0, "" );
237
238 REQUIRE( character.Double() == 7.0 );
239 REQUIRE( unsignedCharacter.Double() == 7.0 );
240 REQUIRE( shortInteger.Double() == 7.0 );
241 REQUIRE( unsignedShort.Double() == 7.0 );
242 REQUIRE( integerSource.Double() == 7.0 );
243 REQUIRE( unsignedInteger.Double() == 7.0 );
244 REQUIRE( longInteger.Double() == 7.0 );
245 REQUIRE( unsignedLong.Double() == 7.0 );
246 REQUIRE( longLongInteger.Double() == 7.0 );
247 REQUIRE( unsignedLongLong.Double() == 7.0 );
248 REQUIRE( single.Double() == 7.0 );
249 REQUIRE( real.Int() == 7 );
250
251 fitsHeaderCard<> target( "TARGET", 9, "" );
252 REQUIRE( target.type( fitsType<unsigned char>() ) == mx::error_t::noerror );
253 REQUIRE( target.UChar() == 9 );
254 REQUIRE( target.type( fitsType<char>() ) == mx::error_t::noerror );
255 REQUIRE( target.Char() == 9 );
256 REQUIRE( target.type( fitsType<short>() ) == mx::error_t::noerror );
257 REQUIRE( target.Short() == 9 );
258 REQUIRE( target.type( fitsType<unsigned short>() ) == mx::error_t::noerror );
259 REQUIRE( target.UShort() == 9 );
260 REQUIRE( target.type( fitsType<int>() ) == mx::error_t::noerror );
261 REQUIRE( target.Int() == 9 );
262 REQUIRE( target.type( fitsType<unsigned int>() ) == mx::error_t::noerror );
263 REQUIRE( target.UInt() == 9U );
264 REQUIRE( target.type( fitsType<long>() ) == mx::error_t::noerror );
265 REQUIRE( target.Long() == 9L );
266 REQUIRE( target.type( fitsType<unsigned long>() ) == mx::error_t::noerror );
267 REQUIRE( target.ULong() == 9UL );
268 REQUIRE( target.type( fitsType<long long>() ) == mx::error_t::noerror );
269 REQUIRE( target.LongLong() == 9LL );
271 REQUIRE( target.ULongLong() == 9ULL );
272 REQUIRE( target.type( fitsType<float>() ) == mx::error_t::noerror );
273 REQUIRE( target.Float() == 9.0F );
274 REQUIRE( target.type( fitsType<double>() ) == mx::error_t::noerror );
275 REQUIRE( target.Double() == 9.0 );
276 REQUIRE( target.type( fitsType<double>() ) == mx::error_t::noerror );
277
278 fitsHeaderCard<> textTarget( "TEXT", 11, "" );
279 REQUIRE( textTarget.type( fitsType<std::string>() ) == mx::error_t::noerror );
280 REQUIRE( textTarget.String() == "11" );
281
282 fitsHeaderCard<> unparsed( "UNPARSED", "12", fitsType<int>() );
283 REQUIRE_FALSE( unparsed.valueGood() );
284 REQUIRE( unparsed.type( fitsType<double>() ) == mx::error_t::noerror );
285 REQUIRE( unparsed.type() == fitsType<double>() );
286 REQUIRE_FALSE( unparsed.valueGood() );
287
288 fitsHeaderCard<> unsetType( "UNSETTYPE" );
289 REQUIRE( unsetType.type( fitsType<float>() ) == mx::error_t::noerror );
290 REQUIRE( unsetType.type() == fitsType<float>() );
291}
292
293/** \brief Verifies fitsHeaderCard reports unsupported source and target conversions without undefined values.
294 *
295 * \ingroup fitsHeaderCard_unit_tests
296 */
297TEST_CASE( "Rejecting unsupported FITS header card conversions", "[ioutils::fits::fitsHeaderCard]" )
298{
300 testHeaderCard<> source;
301
302 source.forceTypeWithValue( fitsType<std::complex<float>>() );
303 REQUIRE( source.Int( &error ) == 0 );
304 REQUIRE( error == mx::error_t::notimpl );
305 source.forceTypeWithValue( fitsType<std::complex<double>>() );
306 REQUIRE( source.Int( &error ) == 0 );
307 REQUIRE( error == mx::error_t::notimpl );
308 source.forceTypeWithValue( fitsType<fitsCommentType>() );
309 REQUIRE( source.Int( &error ) == 0 );
310 REQUIRE( error == mx::error_t::invalidarg );
311 source.forceTypeWithValue( fitsType<fitsHistoryType>() );
312 REQUIRE( source.Int( &error ) == 0 );
313 REQUIRE( error == mx::error_t::invalidarg );
314 source.forceTypeWithValue( fitsType<fitsContinueType>() );
315 REQUIRE( source.Int( &error ) == 0 );
316 REQUIRE( error == mx::error_t::invalidarg );
317 source.forceTypeWithValue( fitsType<std::string>() );
318 REQUIRE( source.Int( &error ) == 0 );
319 REQUIRE( error == mx::error_t::invalidarg );
320 source.forceTypeWithValue( -12345 );
321 REQUIRE( source.Int( &error ) == 0 );
322 REQUIRE( error == mx::error_t::invalidarg );
323
324 source.forceTypeWithValue( fitsType<std::complex<float>>() );
325 REQUIRE( source.type( fitsType<int>() ) == mx::error_t::notimpl );
326
327 fitsHeaderCard<> target( "TARGET", 1, "" );
328 REQUIRE( target.type( fitsType<std::complex<float>>() ) == mx::error_t::notimpl );
329 REQUIRE( target.type( fitsType<std::complex<double>>() ) == mx::error_t::notimpl );
333 REQUIRE( target.type( -12345 ) == mx::error_t::invalidarg );
334
335 testHeaderCard<> special;
337 {
338 special.forceTypeWithValue( type );
339 REQUIRE( special.String( &error ).empty() );
340 REQUIRE( error == mx::error_t::noerror );
341 }
342
343 special.forceTypeWithValue( fitsType<std::string>() );
344 REQUIRE( special.String( &error ).empty() );
345 REQUIRE( error == mx::error_t::noerror );
346 REQUIRE( special.valueStrGood() );
347
348 special.forceTypeWithValue( -12345 );
349 REQUIRE( special.String( &error ).empty() );
350 REQUIRE( error == mx::error_t::invalidarg );
351
352 fitsHeaderCard<> unset( "UNSET" );
353 REQUIRE( unset.String( &error ).empty() );
354 REQUIRE( error == mx::error_t::paramnotset );
355
356 fitsHeaderCard<> continuation( "CONTINUE", "", "'continued'" );
357 fitsHeaderCard<> numeric( "NUMBER", 1, "" );
358 REQUIRE( numeric.appendContinue( continuation ) == mx::error_t::invalidarg );
359 testHeaderCard<> missingString;
360 missingString.forceTypeWithValue( fitsType<std::string>() );
361 REQUIRE( missingString.appendContinue( continuation ) == mx::error_t::invalidconfig );
362}
363
364/** \brief Verifies writing every supported fitsHeaderCard representation through CFITSIO.
365 *
366 * \ingroup fitsHeaderCard_unit_tests
367 */
368TEST_CASE( "Writing FITS header card types", "[ioutils::fits::fitsHeaderCard]" )
369{
370 temporaryFitsFile file;
371
372 fitsHeaderCard<> boolean( "BOOLEAN", true, "bool" );
373 fitsHeaderCard<> character( "CHAR", static_cast<char>( 1 ), "char" );
374 fitsHeaderCard<> unsignedCharacter( "UCHAR", static_cast<unsigned char>( 2 ), "uchar" );
375 fitsHeaderCard<> shortInteger( "SHORT", static_cast<short>( 3 ), "short" );
376 fitsHeaderCard<> unsignedShort( "USHORT", static_cast<unsigned short>( 4 ), "ushort" );
377 fitsHeaderCard<> integer( "INT", 5, "int" );
378 fitsHeaderCard<> unsignedInteger( "UINT", 6U, "uint" );
379 fitsHeaderCard<> longInteger( "LONG", 7L, "long" );
380 fitsHeaderCard<> unsignedLong( "ULONG", 8UL, "ulong" );
381 fitsHeaderCard<> longLongInteger( "LONGLONG", 9LL, "long long" );
382 fitsHeaderCard<> unsignedLongLong( "ULONGLONG", 10ULL, "unsigned long long" );
383 fitsHeaderCard<> single( "FLOAT", 11.5F, "float" );
384 fitsHeaderCard<> real( "DOUBLE", 12.5, "double" );
385 fitsHeaderCard<> text( "STRING", "text", "string" );
386 fitsHeaderCard<> serialized( "SERIAL", "13", fitsType<int>(), "serialized" );
387 fitsHeaderCard<> comment( "COMMENT", fitsCommentType(), "comment text" );
388 fitsHeaderCard<> history( "HISTORY", fitsHistoryType(), "history text" );
389
390 REQUIRE( boolean.write( file.get() ) == mx::error_t::noerror );
391 REQUIRE( character.write( file.get() ) == mx::error_t::noerror );
392 REQUIRE( unsignedCharacter.write( file.get() ) == mx::error_t::noerror );
393 REQUIRE( shortInteger.write( file.get() ) == mx::error_t::noerror );
394 REQUIRE( unsignedShort.write( file.get() ) == mx::error_t::noerror );
395 REQUIRE( integer.write( file.get() ) == mx::error_t::noerror );
396 REQUIRE( unsignedInteger.write( file.get() ) == mx::error_t::noerror );
397 REQUIRE( longInteger.write( file.get() ) == mx::error_t::noerror );
398 REQUIRE( unsignedLong.write( file.get() ) == mx::error_t::noerror );
399 REQUIRE( longLongInteger.write( file.get() ) == mx::error_t::noerror );
400 REQUIRE( unsignedLongLong.write( file.get() ) == mx::error_t::noerror );
401 REQUIRE( single.write( file.get() ) == mx::error_t::noerror );
402 REQUIRE( real.write( file.get() ) == mx::error_t::noerror );
403 REQUIRE( text.write( file.get() ) == mx::error_t::noerror );
404 REQUIRE( serialized.write( file.get() ) == mx::error_t::noerror );
405 REQUIRE( comment.write( file.get() ) == mx::error_t::noerror );
406 REQUIRE( history.write( file.get() ) == mx::error_t::noerror );
407
408 fitsHeaderCard<> invalid( "INVALID", -12345 );
409 REQUIRE( invalid.write( file.get() ) == mx::error_t::invalidarg );
410 fitsHeaderCard<> complexSingle( "COMPLEX", std::complex<float>( 1.0F, 2.0F ), "complex" );
411 REQUIRE( complexSingle.write( file.get() ) == mx::error_t::invalidarg );
412}
413
414/// Verify conversion of types
415/**
416 * \ingroup fitsHeaderCard_unit_tests
417 */
418TEST_CASE( "fitsHeaderCard setting types", "[ioutils::fits::fitsHeaderCard]" )
419{
420 SECTION( "a fitsHeaderCard constructed with char type" )
421 {
422 SECTION( "setting a char from a string" )
423 {
424 fitsHeaderCard fhc( "KEYWORD", "39", fitsType<char>(), "this comment" );
425
426 REQUIRE( fhc.keyword() == "KEYWORD" );
427 std::string s = fhc.valueStr();
428 REQUIRE( s == "39" );
429 REQUIRE( fhc.type() == fitsType<char>() );
430 REQUIRE( fhc.valueGood() == false );
431 REQUIRE( fhc.valueStrGood() == true );
432 REQUIRE( fhc.comment() == "this comment" );
433
434 char c = fhc.value<char>();
435 REQUIRE( c == 39 );
436 REQUIRE( fhc.valueGood() == true );
437
438 int i = fhc.value<int>();
439 REQUIRE( i == 39 );
440 REQUIRE( fhc.type() == fitsType<char>() );
441
442 REQUIRE( fhc.valueGood() == true );
443 REQUIRE( fhc.valueStrGood() == true );
444
445 fhc.type( fitsType<int>() );
446 REQUIRE( fhc.type() == fitsType<int>() );
447 REQUIRE( fhc.Int() == 39 );
448 REQUIRE( fhc.valueGood() == true );
449 REQUIRE( fhc.valueStrGood() == false );
450
451 s = fhc.valueStr();
452 REQUIRE( s == "39" );
453 REQUIRE( fhc.valueStrGood() == true );
454 }
455 SECTION( "setting a char from a char" )
456 {
457 fitsHeaderCard fhc( "KEYWORD", static_cast<char>( 39 ), "this comment" );
458
459 REQUIRE( fhc.keyword() == "KEYWORD" );
460 REQUIRE( fhc.type() == fitsType<char>() );
461 REQUIRE( fhc.valueGood() == true );
462 REQUIRE( fhc.valueStrGood() == false );
463 REQUIRE( fhc.comment() == "this comment" );
464
465 char c = fhc.value<char>();
466 REQUIRE( c == 39 );
467 REQUIRE( fhc.valueGood() == true );
468
469 // Test that valueStr stayed false and then read it
470 REQUIRE( fhc.valueStrGood() == false );
471 std::string s = fhc.valueStr();
472 REQUIRE( s == "39" );
473 REQUIRE( fhc.valueStrGood() == true );
474
475 int i = fhc.value<int>();
476 REQUIRE( i == 39 );
477 REQUIRE( fhc.type() == fitsType<char>() );
478
479 REQUIRE( fhc.valueGood() == true );
480 REQUIRE( fhc.valueStrGood() == true );
481
482 mx::error_t errc = fhc.type( fitsType<int>() );
483 REQUIRE( !errc );
484
485 REQUIRE( fhc.type() == fitsType<int>() );
486 errc = mx::error_t::error;
487 REQUIRE( fhc.Int( &errc ) == 39 );
488 REQUIRE( errc == mx::error_t::noerror );
489
490 REQUIRE( fhc.valueGood() == true );
491 REQUIRE( fhc.valueStrGood() == false );
492
493 s = fhc.valueStr();
494 REQUIRE( s == "39" );
495 REQUIRE( fhc.valueStrGood() == true );
496 }
497 }
498}
499
500/// Removing white space around string values
501/**
502 * \ingroup fitsHeaderCard_unit_tests
503 */
504TEST_CASE( "Removing white space around string values", "[ioutils::fits::fitsHeaderCard]" )
505{
506 // clang-format off
507 #ifdef MXLIBTEST_DOXYGEN_REF
508 fitsHeaderCard fhc;
509 mx::error_t errc;
510 fhc.value( mx::meta::tagT<std::string>(), errc );
511 #endif
512 // clang-format on
513
514 SECTION( "typical case" )
515 {
516 fitsHeaderCard fhc( "KEYTEST", "'simple '", "comment" );
517 REQUIRE( fhc.String() == "simple " );
518 }
519
520 SECTION( "no ' and no space" )
521 {
522 fitsHeaderCard fhc( "KEYTEST", "simple", "comment" );
523 REQUIRE( fhc.String() == "simple" );
524 }
525
526 SECTION( "no spaces" )
527 {
528 fitsHeaderCard fhc( "KEYTEST", "'simple'", "comment" );
529 REQUIRE( fhc.String() == "simple" );
530 }
531
532 SECTION( "no '" )
533 {
534 fitsHeaderCard fhc( "KEYTEST", "simple ", "comment" );
535 REQUIRE( fhc.String() == "simple" );
536 }
537
538 SECTION( "space at beginning" )
539 {
540 fitsHeaderCard fhc( "KEYTEST", "' simple '", "comment" );
541 REQUIRE( fhc.String() == " simple " );
542 }
543
544 SECTION( "spaces at beginning, no spaces at end" )
545 {
546 fitsHeaderCard fhc( "KEYTEST", "' simple'", "comment" );
547 REQUIRE( fhc.String() == " simple" );
548 }
549
550 SECTION( "spaces at beginning, no '" )
551 {
552 fitsHeaderCard fhc( "KEYTEST", " simple ", "comment" );
553 REQUIRE( fhc.String() == "simple" );
554 }
555
556 SECTION( "empty" )
557 {
558 fitsHeaderCard fhc( "KEYTEST", "", "comment" );
559 REQUIRE( fhc.String() == "" );
560 }
561
562 SECTION( "one '" )
563 {
564 fitsHeaderCard fhc( "KEYTEST", "'", "comment" );
565 REQUIRE( fhc.String() == "" );
566 }
567
568 SECTION( "two ''" )
569 {
570 fitsHeaderCard fhc( "KEYTEST", "''", "comment" );
571 REQUIRE( fhc.String() == "" );
572 }
573
574 SECTION( "two '', spaces" )
575 {
576 fitsHeaderCard fhc( "KEYTEST", "' '", "comment" );
577 REQUIRE( fhc.String() == " " );
578 }
579
580 SECTION( "spaces only" )
581 {
582 fitsHeaderCard fhc( "KEYTEST", " ", "comment" );
583 REQUIRE( fhc.String() == "" );
584 }
585
586 SECTION( "' part of value" )
587 {
588 fitsHeaderCard fhc( "KEYTEST", "z'", "comment" );
589 REQUIRE( fhc.String() == "z'" );
590 }
591
592 SECTION( "' part of value at end with spaces" )
593 {
594 fitsHeaderCard fhc( "KEYTEST", "'z' '", "comment" );
595 REQUIRE( fhc.String() == "z' " );
596 }
597
598 SECTION( "' part of value at beginning with spaces" )
599 {
600 fitsHeaderCard fhc( "KEYTEST", "''z '", "comment" );
601 REQUIRE( fhc.String() == "'z " );
602 }
603
604 SECTION( "spaces before and after '' with ' in value surrounded by spaces" )
605 {
606 fitsHeaderCard fhc( "KEYTEST", " ' 'z ' ", "comment" );
607 REQUIRE( fhc.String() == " 'z " );
608 }
609}
610
611/// CONTINUE-ing a card
612/**
613 * \ingroup fitsHeaderCard_unit_tests
614 */
615TEST_CASE( "CONTINUE-ing a card", "[ioutils::fits::fitsHeaderCard]" )
616{
617 SECTION( "normal CONTINUE, trailing space" )
618 {
619 fitsHeaderCard fhc( "KEYTEST", "'one,two,three,&'", "" );
620 fitsHeaderCard fhcc( "CONTINUE", "", "'four,five,six' / the comment" );
621
622 REQUIRE( fhc.appendContinue( fhcc ) == error_t::noerror );
623
624 REQUIRE( fhc.keyword() == "KEYTEST" );
625 REQUIRE( fhc.String() == "one,two,three,four,five,six" );
626 REQUIRE( fhc.comment() == "the comment" );
627 }
628
629 SECTION( "normal CONTINUE, trailing space" )
630 {
631 fitsHeaderCard fhc( "KEYTEST", "'one two three &'", "" );
632 fitsHeaderCard fhcc( "CONTINUE", "", "'four five six' / the comment" );
633
634 REQUIRE( fhc.appendContinue( fhcc ) == error_t::noerror );
635
636 REQUIRE( fhc.keyword() == "KEYTEST" );
637 REQUIRE( fhc.String() == "one two three four five six" );
638 REQUIRE( fhc.comment() == "the comment" );
639 }
640
641 SECTION( "normal CONTINUE, leading space" )
642 {
643 fitsHeaderCard fhc( "KEYTEST", "'one two three&'", "" );
644 fitsHeaderCard fhcc( "CONTINUE", "", "' four five six' / the comment" );
645
646 REQUIRE( fhc.appendContinue( fhcc ) == error_t::noerror );
647
648 REQUIRE( fhc.keyword() == "KEYTEST" );
649 REQUIRE( fhc.String() == "one two three four five six" );
650 REQUIRE( fhc.comment() == "the comment" );
651 }
652}
653
654/** \brief Verifies the typed card updates used for HCI coadd provenance.
655 *
656 * \ingroup fitsHeaderCard_unit_tests
657 */
658TEST_CASE( "Updating HCI coadd header cards", "[ioutils::fits::fitsHeaderCard][hciReduce]" )
659{
660 fitsHeaderCard date( "MJD-OBS", "2000-01-01T00:00:00.000000000Z", "mean observation time" );
661
662 REQUIRE( date.value( "2000-01-01T12:00:00.000000000Z" ) == error_t::noerror );
663 REQUIRE( date.String() == "2000-01-01T12:00:00.000000000Z" );
664 REQUIRE( date.comment() == "mean observation time" );
665
666 fitsHeaderCard angle( "ANGLE", 10.0, "derotation angle" );
667 REQUIRE( angle.value( 15.5 ) == error_t::noerror );
668 REQUIRE( angle.value<double>() == Approx( 15.5 ) );
669 REQUIRE( angle.comment( "mean derotation angle" ) == error_t::noerror );
670 REQUIRE( angle.comment() == "mean derotation angle" );
671}
672
673} // namespace fitsHeaderCardTest
674} // namespace fitsTest
675} // namespace unitTest
676} // namespace mx
Class to manage the three components of a FITS header card.
mx::error_t write(fitsfile *fptr)
Writes this card to a FITS file using fits_write_key.
int type() const
Get the type.
std::string value(meta::tagT< std::string >, mx::error_t &errc)
Get value tag dispatcher for std::string.
unsigned short UShort(error_t *errc=nullptr)
Get the value as an unsigned short.
unsigned char UChar(error_t *errc=nullptr)
Get the value as an unsigned char.
float Float(error_t *errc=nullptr)
Get the value as a float.
double Double(error_t *errc=nullptr)
Get the value as a double.
long long LongLong(error_t *errc=nullptr)
Get the value as a long long.
char Char(error_t *errc=nullptr)
Get the value as a char.
long Long(error_t *errc=nullptr)
Get the value as a long.
bool valueStrGood()
Get the current value string good flag.
unsigned int UInt(error_t *errc=nullptr)
Get the value as an unsigned int.
const std::string & keyword() const
Get the keyword.
short Short(error_t *errc=nullptr)
Get the value as a short.
bool valueGood()
Get the current value good flag.
unsigned long long ULongLong(error_t *errc=nullptr)
Get the value as an unsigned long long.
std::string valueStr()
Get the current value string.
unsigned long ULong(error_t *errc=nullptr)
Get the value as an unsigned long.
const std::string & comment()
Get the comment.
std::string String(error_t *errc=nullptr)
Get the value as a string.
int Int(error_t *errc=nullptr)
Get the value as a int.
A class to work with a FITS header card.
error_t
The mxlib error codes.
Definition error_t.hpp:26
@ notimpl
A component or technique is not implemented.
Definition error_t.hpp:31
@ noerror
No error has occurred.
Definition error_t.hpp:27
@ paramnotset
A parameter was not set.
Definition error_t.hpp:32
@ invalidconfig
A config setting was invalid.
Definition error_t.hpp:30
@ invalidarg
An argument was invalid.
Definition error_t.hpp:29
@ error
A general error has occurred.
Definition error_t.hpp:28
TEST_CASE("Round-tripping native FITS header card values", "[ioutils::fits::fitsHeaderCard]")
Verifies construction, copying, assignment, and scalar access for every supported numeric card type.
constexpr int fitsType()
Return the cfitsio constant for a given data type.
The mxlib c++ namespace.
Definition mxlib.hpp:37
Empty type for tag dispatching.
Definition tagT.hpp:44