Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 238 additions & 0 deletions tests/test_image_converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <vector>
#include <sys/stat.h> // for mkfifo
#include <ctime>
Expand Down Expand Up @@ -60,6 +61,10 @@ class TestDirectory
std::to_string( std::time( nullptr ) ) ) )
.string();
std::filesystem::create_directories( test_dir );

// Create database directory for test data
database_dir = test_dir + "/test-database";
std::filesystem::create_directories( database_dir );
}

~TestDirectory() { std::filesystem::remove_all( test_dir ); }
Expand All @@ -69,6 +74,7 @@ class TestDirectory
TestDirectory &operator=( const TestDirectory & ) = delete;

const std::string &path() const { return test_dir; }
const std::string &get_database_path() const { return database_dir; }

void create_test_files()
{
Expand Down Expand Up @@ -119,8 +125,51 @@ class TestDirectory
}
}

/// Creates a test data file (camera or illuminant) with the specified header data
/// @param type The type of test data to create (e.g. camera or illuminant)
/// @param header_data JSON object containing the header data to include
/// @return The full path to the created file
std::string create_test_data_file(
const std::string &type, const nlohmann::json &header_data )
{
// Generate random filename
static int file_counter = 0;
std::string filename = "test_data_" + std::to_string( ++file_counter ) +
"_" + std::to_string( std::time( nullptr ) ) +
".json";

// Create target directory dynamically based on type
std::string target_dir = database_dir + "/" + type;
std::filesystem::create_directories( target_dir );
std::string file_path = target_dir + "/" + filename;

// Create JSON object using nlohmann/json
nlohmann::json json_data;

// Start with default header and merge user data
nlohmann::json header = header_data;

// Build spectral_data object
nlohmann::json spectral_data = { { "units", "relative" },
{ "index",
{ { "main", { "R", "G", "B" } } } },
{ "data", nlohmann::json::object() } };

// Assemble final JSON
json_data["header"] = header;
json_data["spectral_data"] = spectral_data;

// Write to file with pretty formatting
std::ofstream file( file_path );
file << json_data.dump( 4 ) << std::endl;
file.close();

return file_path;
}

private:
std::string test_dir;
std::string database_dir;
};

/// Verifies that collect_image_files can traverse a directory, identify valid RAW image files,
Expand Down Expand Up @@ -522,6 +571,191 @@ void test_fix_metadata_unsupported_type()
OIIO_CHECK_EQUAL( spec.find_attribute( "Make" ), nullptr );
}

/// Helper function to set up test environment and capture output for parse_parameters tests
struct ParseParametersTestResult
{
bool success;
std::string output;
};

/// Executes a parse_parameters test with the given command-line arguments and captures its output.
///
/// This helper function encapsulates all the common setup required for testing ImageConverter::parse_parameters,
/// including environment configuration, argument parsing, stdout capture, and cleanup.
///
/// @param args Vector of command-line arguments to pass to parse_parameters (excluding program name).
/// For example, {"--list-cameras"} or {"--list-illuminants", "--verbose"}.
/// @param database_path Path to the test database directory (optional, uses default if not provided)
///
/// @return ParseParametersTestResult containing:
/// - success: true if parse_parameters executed successfully, false if argument parsing failed
/// - output: captured stdout output from the parse_parameters execution
ParseParametersTestResult run_parse_parameters_test(
const std::vector<std::string> &args,
const std::string &database_path = "" )
{
// Set up test data path to use the provided database path or default
set_env_var( "RAWTOACES_DATA_PATH", database_path.c_str() );

// Create ImageConverter instance
rta::util::ImageConverter converter;

// Create argument parser and initialize it
OIIO::ArgParse arg_parser;
converter.init_parser( arg_parser );

// Convert args to char* array for parsing
std::vector<const char *> argv;
argv.push_back( "rawtoaces" ); // Program name
for ( const auto &arg: args )
{
argv.push_back( arg.c_str() );
}

// Parse the arguments
int parse_result =
arg_parser.parse_args( static_cast<int>( argv.size() ), argv.data() );
if ( parse_result != 0 )
{
unset_env_var( "RAWTOACES_DATA_PATH" );
return { false, "" };
}

// Capture stdout to verify the output
std::ostringstream captured_output;
std::streambuf *original_cout = std::cout.rdbuf();
std::cout.rdbuf( captured_output.rdbuf() );

// Call parse_parameters
bool result = converter.parse_parameters( arg_parser );

// Restore original cout
std::cout.rdbuf( original_cout );

// Clean up environment variable
unset_env_var( "RAWTOACES_DATA_PATH" );

return { result, captured_output.str() };
}

/// This test verifies that when --list-cameras is provided, the method
/// calls supported_cameras() and outputs the camera list, then exits
void test_parse_parameters_list_cameras()
{
std::cout << std::endl
<< "test_parse_parameters_list_cameras()" << std::endl;

// Create test directory with dynamic database
TestDirectory test_dir;

// Create test camera data files
test_dir.create_test_data_file(
"camera", { { "manufacturer", "Canon" }, { "model", "EOS R6" } } );
test_dir.create_test_data_file(
"camera", { { "manufacturer", "Mamiya" }, { "model", "Mamiya 7" } } );

// Run the test with --list-cameras argument using the dynamic database
auto result = run_parse_parameters_test(
{ "--list-cameras" }, test_dir.get_database_path() );

// The method should return true (though it calls exit in real usage)
OIIO_CHECK_EQUAL( result.success, true );

// Verify the output contains expected camera list information
OIIO_CHECK_EQUAL(
result.output.find(
"Spectral sensitivity data is available for the following cameras:" ) !=
std::string::npos,
true );

// Verify that actual camera names from test data are present
// The format is "manufacturer / model" as defined in supported_cameras()
OIIO_CHECK_EQUAL(
result.output.find( "Canon / EOS R6" ) != std::string::npos, true );
OIIO_CHECK_EQUAL(
result.output.find( "Mamiya / Mamiya 7" ) != std::string::npos, true );

// Count occurrences of " / " to verify we have 2 camera entries
size_t camera_count = 0;
size_t pos = 0;
while ( ( pos = result.output.find( " / ", pos ) ) != std::string::npos )
{
camera_count++;
pos += 3; // Move past " / "
}
OIIO_CHECK_EQUAL( camera_count, 2 );
}

/// This test verifies that when --list-illuminants is provided, the method
/// calls supported_illuminants() and outputs the illuminant list, then exits
void test_parse_parameters_list_illuminants()
{
std::cout << std::endl
<< "test_parse_parameters_list_illuminants()" << std::endl;

// Create test directory with dynamic database
TestDirectory test_dir;

// Create test illuminant data file
test_dir.create_test_data_file(
"illuminant", { { "illuminant", "my-illuminant" } } );

// Run the test with --list-illuminants argument using the dynamic database
auto result = run_parse_parameters_test(
{ "--list-illuminants" }, test_dir.get_database_path() );

// The method should return true (though it calls exit in real usage)
OIIO_CHECK_EQUAL( result.success, true );

// Verify the output contains expected illuminant list information
OIIO_CHECK_EQUAL(
result.output.find( "The following illuminants are supported:" ) !=
std::string::npos,
true );

// Verify that actual illuminant names from test data are present
// The hardcoded illuminant types should be present
OIIO_CHECK_EQUAL(
result.output.find( "Day-light (e.g., D60, D6025)" ) !=
std::string::npos,
true );
OIIO_CHECK_EQUAL(
result.output.find( "Blackbody (e.g., 3200K)" ) != std::string::npos,
true );

// Verify that the specific illuminant from our test data is present
OIIO_CHECK_EQUAL(
result.output.find( "my-illuminant" ) != std::string::npos, true );

// Verify we have exactly 3 illuminants total (2 hardcoded + 1 from test data)
// Count newlines in the illuminant list section to verify count
size_t illuminant_count = 0;
size_t start_pos =
result.output.find( "The following illuminants are supported:" );
if ( start_pos != std::string::npos )
{
size_t end_pos = result.output.find(
"\n\n", start_pos ); // Look for double newline (end of list)
if ( end_pos == std::string::npos )
{
end_pos = result.output.length(); // If no double newline, go to end
}

std::string illuminant_section =
result.output.substr( start_pos, end_pos - start_pos );
size_t pos = 0;
while ( ( pos = illuminant_section.find( "\n", pos ) ) !=
std::string::npos )
{
illuminant_count++;
pos += 1;
}
// Subtract 1 for the header line
illuminant_count = ( illuminant_count > 0 ) ? illuminant_count - 1 : 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to drop that part. Makes test too fragile. If we decide to add some post-message after list of illuminants - it will blow up, which might not what we want. At the same time if we decide to add a new hardcoded illuminant, this test won't brake, even though we might want it to.

}
OIIO_CHECK_EQUAL( illuminant_count, 3 );
}

int main( int, char ** )
{
try
Expand All @@ -547,6 +781,10 @@ int main( int, char ** )
test_fix_metadata_source_missing();
test_fix_metadata_source_missing();
test_fix_metadata_unsupported_type();

// Tests for parse_parameters
test_parse_parameters_list_cameras();
test_parse_parameters_list_illuminants();
}
catch ( const std::exception &e )
{
Expand Down
Loading