1111#include < filesystem>
1212#include < fstream>
1313#include < iostream>
14+ #include < nlohmann/json.hpp>
1415#include < vector>
1516#include < sys/stat.h> // for mkfifo
1617#include < ctime>
@@ -60,6 +61,10 @@ class TestDirectory
6061 std::to_string ( std::time ( nullptr ) ) ) )
6162 .string ();
6263 std::filesystem::create_directories ( test_dir );
64+
65+ // Create database directory for test data
66+ database_dir = test_dir + " /test-database" ;
67+ std::filesystem::create_directories ( database_dir );
6368 }
6469
6570 ~TestDirectory () { std::filesystem::remove_all ( test_dir ); }
@@ -69,6 +74,7 @@ class TestDirectory
6974 TestDirectory &operator =( const TestDirectory & ) = delete ;
7075
7176 const std::string &path () const { return test_dir; }
77+ const std::string &get_database_path () const { return database_dir; }
7278
7379 void create_test_files ()
7480 {
@@ -119,8 +125,51 @@ class TestDirectory
119125 }
120126 }
121127
128+ // / Creates a test data file (camera or illuminant) with the specified header data
129+ // / @param type The type of test data to create (e.g. camera or illuminant)
130+ // / @param header_data JSON object containing the header data to include
131+ // / @return The full path to the created file
132+ std::string create_test_data_file (
133+ const std::string &type, const nlohmann::json &header_data )
134+ {
135+ // Generate random filename
136+ static int file_counter = 0 ;
137+ std::string filename = " test_data_" + std::to_string ( ++file_counter ) +
138+ " _" + std::to_string ( std::time ( nullptr ) ) +
139+ " .json" ;
140+
141+ // Create target directory dynamically based on type
142+ std::string target_dir = database_dir + " /" + type;
143+ std::filesystem::create_directories ( target_dir );
144+ std::string file_path = target_dir + " /" + filename;
145+
146+ // Create JSON object using nlohmann/json
147+ nlohmann::json json_data;
148+
149+ // Start with default header and merge user data
150+ nlohmann::json header = header_data;
151+
152+ // Build spectral_data object
153+ nlohmann::json spectral_data = { { " units" , " relative" },
154+ { " index" ,
155+ { { " main" , { " R" , " G" , " B" } } } },
156+ { " data" , nlohmann::json::object () } };
157+
158+ // Assemble final JSON
159+ json_data[" header" ] = header;
160+ json_data[" spectral_data" ] = spectral_data;
161+
162+ // Write to file with pretty formatting
163+ std::ofstream file ( file_path );
164+ file << json_data.dump ( 4 ) << std::endl;
165+ file.close ();
166+
167+ return file_path;
168+ }
169+
122170private:
123171 std::string test_dir;
172+ std::string database_dir;
124173};
125174
126175// / Verifies that collect_image_files can traverse a directory, identify valid RAW image files,
@@ -522,6 +571,191 @@ void test_fix_metadata_unsupported_type()
522571 OIIO_CHECK_EQUAL ( spec.find_attribute ( " Make" ), nullptr );
523572}
524573
574+ // / Helper function to set up test environment and capture output for parse_parameters tests
575+ struct ParseParametersTestResult
576+ {
577+ bool success;
578+ std::string output;
579+ };
580+
581+ // / Executes a parse_parameters test with the given command-line arguments and captures its output.
582+ // /
583+ // / This helper function encapsulates all the common setup required for testing ImageConverter::parse_parameters,
584+ // / including environment configuration, argument parsing, stdout capture, and cleanup.
585+ // /
586+ // / @param args Vector of command-line arguments to pass to parse_parameters (excluding program name).
587+ // / For example, {"--list-cameras"} or {"--list-illuminants", "--verbose"}.
588+ // / @param database_path Path to the test database directory (optional, uses default if not provided)
589+ // /
590+ // / @return ParseParametersTestResult containing:
591+ // / - success: true if parse_parameters executed successfully, false if argument parsing failed
592+ // / - output: captured stdout output from the parse_parameters execution
593+ ParseParametersTestResult run_parse_parameters_test (
594+ const std::vector<std::string> &args,
595+ const std::string &database_path = " " )
596+ {
597+ // Set up test data path to use the provided database path or default
598+ set_env_var ( " RAWTOACES_DATA_PATH" , database_path.c_str () );
599+
600+ // Create ImageConverter instance
601+ rta::util::ImageConverter converter;
602+
603+ // Create argument parser and initialize it
604+ OIIO::ArgParse arg_parser;
605+ converter.init_parser ( arg_parser );
606+
607+ // Convert args to char* array for parsing
608+ std::vector<const char *> argv;
609+ argv.push_back ( " rawtoaces" ); // Program name
610+ for ( const auto &arg: args )
611+ {
612+ argv.push_back ( arg.c_str () );
613+ }
614+
615+ // Parse the arguments
616+ int parse_result =
617+ arg_parser.parse_args ( static_cast <int >( argv.size () ), argv.data () );
618+ if ( parse_result != 0 )
619+ {
620+ unset_env_var ( " RAWTOACES_DATA_PATH" );
621+ return { false , " " };
622+ }
623+
624+ // Capture stdout to verify the output
625+ std::ostringstream captured_output;
626+ std::streambuf *original_cout = std::cout.rdbuf ();
627+ std::cout.rdbuf ( captured_output.rdbuf () );
628+
629+ // Call parse_parameters
630+ bool result = converter.parse_parameters ( arg_parser );
631+
632+ // Restore original cout
633+ std::cout.rdbuf ( original_cout );
634+
635+ // Clean up environment variable
636+ unset_env_var ( " RAWTOACES_DATA_PATH" );
637+
638+ return { result, captured_output.str () };
639+ }
640+
641+ // / This test verifies that when --list-cameras is provided, the method
642+ // / calls supported_cameras() and outputs the camera list, then exits
643+ void test_parse_parameters_list_cameras ()
644+ {
645+ std::cout << std::endl
646+ << " test_parse_parameters_list_cameras()" << std::endl;
647+
648+ // Create test directory with dynamic database
649+ TestDirectory test_dir;
650+
651+ // Create test camera data files
652+ test_dir.create_test_data_file (
653+ " camera" , { { " manufacturer" , " Canon" }, { " model" , " EOS R6" } } );
654+ test_dir.create_test_data_file (
655+ " camera" , { { " manufacturer" , " Mamiya" }, { " model" , " Mamiya 7" } } );
656+
657+ // Run the test with --list-cameras argument using the dynamic database
658+ auto result = run_parse_parameters_test (
659+ { " --list-cameras" }, test_dir.get_database_path () );
660+
661+ // The method should return true (though it calls exit in real usage)
662+ OIIO_CHECK_EQUAL ( result.success , true );
663+
664+ // Verify the output contains expected camera list information
665+ OIIO_CHECK_EQUAL (
666+ result.output .find (
667+ " Spectral sensitivity data is available for the following cameras:" ) !=
668+ std::string::npos,
669+ true );
670+
671+ // Verify that actual camera names from test data are present
672+ // The format is "manufacturer / model" as defined in supported_cameras()
673+ OIIO_CHECK_EQUAL (
674+ result.output .find ( " Canon / EOS R6" ) != std::string::npos, true );
675+ OIIO_CHECK_EQUAL (
676+ result.output .find ( " Mamiya / Mamiya 7" ) != std::string::npos, true );
677+
678+ // Count occurrences of " / " to verify we have 2 camera entries
679+ size_t camera_count = 0 ;
680+ size_t pos = 0 ;
681+ while ( ( pos = result.output .find ( " / " , pos ) ) != std::string::npos )
682+ {
683+ camera_count++;
684+ pos += 3 ; // Move past " / "
685+ }
686+ OIIO_CHECK_EQUAL ( camera_count, 2 );
687+ }
688+
689+ // / This test verifies that when --list-illuminants is provided, the method
690+ // / calls supported_illuminants() and outputs the illuminant list, then exits
691+ void test_parse_parameters_list_illuminants ()
692+ {
693+ std::cout << std::endl
694+ << " test_parse_parameters_list_illuminants()" << std::endl;
695+
696+ // Create test directory with dynamic database
697+ TestDirectory test_dir;
698+
699+ // Create test illuminant data file
700+ test_dir.create_test_data_file (
701+ " illuminant" , { { " illuminant" , " my-illuminant" } } );
702+
703+ // Run the test with --list-illuminants argument using the dynamic database
704+ auto result = run_parse_parameters_test (
705+ { " --list-illuminants" }, test_dir.get_database_path () );
706+
707+ // The method should return true (though it calls exit in real usage)
708+ OIIO_CHECK_EQUAL ( result.success , true );
709+
710+ // Verify the output contains expected illuminant list information
711+ OIIO_CHECK_EQUAL (
712+ result.output .find ( " The following illuminants are supported:" ) !=
713+ std::string::npos,
714+ true );
715+
716+ // Verify that actual illuminant names from test data are present
717+ // The hardcoded illuminant types should be present
718+ OIIO_CHECK_EQUAL (
719+ result.output .find ( " Day-light (e.g., D60, D6025)" ) !=
720+ std::string::npos,
721+ true );
722+ OIIO_CHECK_EQUAL (
723+ result.output .find ( " Blackbody (e.g., 3200K)" ) != std::string::npos,
724+ true );
725+
726+ // Verify that the specific illuminant from our test data is present
727+ OIIO_CHECK_EQUAL (
728+ result.output .find ( " my-illuminant" ) != std::string::npos, true );
729+
730+ // Verify we have exactly 3 illuminants total (2 hardcoded + 1 from test data)
731+ // Count newlines in the illuminant list section to verify count
732+ size_t illuminant_count = 0 ;
733+ size_t start_pos =
734+ result.output .find ( " The following illuminants are supported:" );
735+ if ( start_pos != std::string::npos )
736+ {
737+ size_t end_pos = result.output .find (
738+ " \n\n " , start_pos ); // Look for double newline (end of list)
739+ if ( end_pos == std::string::npos )
740+ {
741+ end_pos = result.output .length (); // If no double newline, go to end
742+ }
743+
744+ std::string illuminant_section =
745+ result.output .substr ( start_pos, end_pos - start_pos );
746+ size_t pos = 0 ;
747+ while ( ( pos = illuminant_section.find ( " \n " , pos ) ) !=
748+ std::string::npos )
749+ {
750+ illuminant_count++;
751+ pos += 1 ;
752+ }
753+ // Subtract 1 for the header line
754+ illuminant_count = ( illuminant_count > 0 ) ? illuminant_count - 1 : 0 ;
755+ }
756+ OIIO_CHECK_EQUAL ( illuminant_count, 3 );
757+ }
758+
525759int main ( int , char ** )
526760{
527761 try
@@ -547,6 +781,10 @@ int main( int, char ** )
547781 test_fix_metadata_source_missing ();
548782 test_fix_metadata_source_missing ();
549783 test_fix_metadata_unsupported_type ();
784+
785+ // Tests for parse_parameters
786+ test_parse_parameters_list_cameras ();
787+ test_parse_parameters_list_illuminants ();
550788 }
551789 catch ( const std::exception &e )
552790 {
0 commit comments