Skip to content

Commit c836971

Browse files
committed
Merge remote-tracking branch 'upstream/main' into add-more-util-tests
2 parents a295889 + a49c1a9 commit c836971

File tree

8 files changed

+201
-34
lines changed

8 files changed

+201
-34
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.DS_Store
22
build/
33
build-coverage/
4-
tests/materials/*.exr
4+
tests/materials/*.exr
5+
__pycache__/

build_scripts/install_deps_linux.bash

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ time sudo apt-get -q -f install -y \
1010
libopencv-dev \
1111
openimageio-tools libopenimageio-dev \
1212
nanobind-dev
13+
14+
pip3 install pytest

build_scripts/install_deps_mac.bash

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
set -ex
44

55
brew install ceres-solver nlohmann-json openimageio nanobind robin-map
6+
7+
python3 -m pip install --break-system-packages pytest

build_scripts/install_deps_windows.bash

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,9 @@ vcpkg x-update-baseline \
88
vcpkg install \
99
--x-install-root="C:/vcpkg/installed" \
1010
--x-manifest-root="./build_scripts"
11+
12+
# Install pip and pytest to the vcpkg Python
13+
# Since vcpkg Python doesn't include pip, install it first using ensurepip
14+
VCPKG_PYTHON="C:/vcpkg/installed/x64-windows/tools/python3/python.exe"
15+
"$VCPKG_PYTHON" -m ensurepip --upgrade
16+
"$VCPKG_PYTHON" -m pip install pytest

build_scripts/install_deps_yum.bash

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
set -ex
44

55
sudo yum install --setopt=tsflags=nodocs -y eigen3-devel ceres-solver-devel json-devel
6-
7-
sudo python -m pip install nanobind
6+
7+
sudo python -m pip install nanobind pytest

src/rawtoaces_util/image_converter.cpp

Lines changed: 79 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1744,53 +1744,67 @@ bool ImageConverter::apply_crop(
17441744
bool ImageConverter::make_output_path(
17451745
std::string &path, const std::string &suffix )
17461746
{
1747-
std::filesystem::path temp_path( path );
1747+
// Validate input path
1748+
if ( path.empty() )
1749+
{
1750+
std::cerr << "ERROR: Empty input path provided." << std::endl;
1751+
return false;
1752+
}
1753+
try
1754+
{
1755+
std::filesystem::path temp_path( path );
17481756

1749-
temp_path.replace_extension();
1750-
temp_path += suffix + ".exr";
1757+
temp_path.replace_extension();
1758+
temp_path += suffix + ".exr";
17511759

1752-
if ( !settings.output_dir.empty() )
1753-
{
1754-
auto new_directory = std::filesystem::path( settings.output_dir );
1760+
if ( !settings.output_dir.empty() )
1761+
{
1762+
auto new_directory = std::filesystem::path( settings.output_dir );
17551763

1756-
auto filename = temp_path.filename();
1757-
auto old_directory = temp_path.remove_filename();
1764+
auto filename = temp_path.filename();
1765+
auto old_directory = temp_path.remove_filename();
17581766

1759-
new_directory = old_directory / new_directory;
1767+
new_directory = old_directory / new_directory;
17601768

1761-
if ( !std::filesystem::exists( new_directory ) )
1762-
{
1763-
if ( settings.create_dirs )
1769+
if ( !std::filesystem::exists( new_directory ) )
17641770
{
1765-
if ( !std::filesystem::create_directory( new_directory ) )
1771+
if ( settings.create_dirs )
1772+
{
1773+
if ( !std::filesystem::create_directory( new_directory ) )
1774+
{
1775+
std::cerr << "ERROR: Failed to create directory "
1776+
<< new_directory << "." << std::endl;
1777+
return false;
1778+
}
1779+
}
1780+
else
17661781
{
1767-
std::cerr << "ERROR: Failed to create directory "
1768-
<< new_directory << "." << std::endl;
1782+
std::cerr << "ERROR: The output directory " << new_directory
1783+
<< " does not exist." << std::endl;
17691784
return false;
17701785
}
17711786
}
1772-
else
1773-
{
1774-
std::cerr << "ERROR: The output directory " << new_directory
1775-
<< " does not exist." << std::endl;
1776-
return false;
1777-
}
1787+
temp_path = std::filesystem::absolute( new_directory / filename );
17781788
}
17791789

1780-
temp_path = std::filesystem::absolute( new_directory / filename );
1781-
}
1790+
if ( !settings.overwrite && std::filesystem::exists( temp_path ) )
1791+
{
1792+
std::cerr
1793+
<< "ERROR: file " << temp_path << " already exists. Use "
1794+
<< "--overwrite to allow overwriting existing files. Skipping "
1795+
<< "this file." << std::endl;
1796+
return false;
1797+
}
17821798

1783-
if ( !settings.overwrite && std::filesystem::exists( temp_path ) )
1799+
path = temp_path.string();
1800+
return true;
1801+
}
1802+
catch ( const std::exception &e )
17841803
{
1785-
std::cerr
1786-
<< "ERROR: file " << temp_path << " already exists. Use "
1787-
<< "--overwrite to allow overwriting existing files. Skipping "
1788-
<< "this file." << std::endl;
1804+
std::cerr << "ERROR: Invalid path format '" << path << "': " << e.what()
1805+
<< std::endl;
17891806
return false;
17901807
}
1791-
1792-
path = temp_path.string();
1793-
return true;
17941808
}
17951809

17961810
bool ImageConverter::save_image(
@@ -1824,6 +1838,40 @@ bool ImageConverter::save_image(
18241838

18251839
bool ImageConverter::process_image( const std::string &input_filename )
18261840
{
1841+
// Early validation: check if input file exists and is valid
1842+
if ( input_filename.empty() )
1843+
{
1844+
if ( settings.verbosity > 0 )
1845+
{
1846+
std::cerr << "ERROR: Empty input filename provided." << std::endl;
1847+
}
1848+
return false;
1849+
}
1850+
1851+
// Validate input file exists
1852+
// Wrap in try-catch to handle filesystem exceptions on Windows
1853+
try
1854+
{
1855+
if ( !std::filesystem::exists( input_filename ) )
1856+
{
1857+
if ( settings.verbosity > 0 )
1858+
{
1859+
std::cerr << "ERROR: Input file does not exist: "
1860+
<< input_filename << std::endl;
1861+
}
1862+
return false;
1863+
}
1864+
}
1865+
catch ( const std::filesystem::filesystem_error &e )
1866+
{
1867+
if ( settings.verbosity > 0 )
1868+
{
1869+
std::cerr << "ERROR: Filesystem error while checking input file '"
1870+
<< input_filename << "': " << e.what() << std::endl;
1871+
}
1872+
return false;
1873+
}
1874+
18271875
std::string output_filename = input_filename;
18281876
if ( !make_output_path( output_filename ) )
18291877
{

tests/CMakeLists.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,30 @@ target_link_libraries(
200200
setup_test_coverage(Test_ImageConverter)
201201
add_test ( NAME Test_ImageConverter COMMAND Test_ImageConverter )
202202

203+
################################################################################
204+
# Python tests
205+
if(RTA_BUILD_PYTHON_BINDINGS)
206+
if(Python_EXECUTABLE)
207+
add_test(
208+
NAME Test_Python_ImageConverter
209+
COMMAND ${Python_EXECUTABLE} -m pytest ${CMAKE_CURRENT_SOURCE_DIR}/python/test_image_converter.py -v
210+
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
211+
)
212+
213+
# Set environment for Python test to find the compiled module
214+
# Handle both single-config (Linux/macOS) and multi-config (Windows) generators
215+
if(WIN32)
216+
set_tests_properties(Test_Python_ImageConverter PROPERTIES
217+
ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}/src/bindings/Release;${CMAKE_BINARY_DIR}/src/bindings;$ENV{PYTHONPATH}"
218+
)
219+
else()
220+
set_tests_properties(Test_Python_ImageConverter PROPERTIES
221+
ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}/src/bindings:$ENV{PYTHONPATH}"
222+
)
223+
endif()
224+
endif()
225+
endif()
226+
203227
################################################################################
204228
# Coverage report generation
205229
if( ENABLE_COVERAGE AND COVERAGE_SUPPORTED )
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: Apache-2.0
3+
# Copyright Contributors to the rawtoaces Project.
4+
5+
"""
6+
Unit tests for rawtoaces Python bindings - ImageConverter class
7+
"""
8+
9+
import pytest
10+
11+
# The PYTHONPATH should be set by CMake to find the compiled module
12+
# No need to manually add paths here as CMake handles this via environment variables
13+
14+
try:
15+
import rawtoaces
16+
except ImportError as e:
17+
pytest.skip(f"rawtoaces module not found. Build the Python bindings first: {e}", allow_module_level=True)
18+
19+
20+
class TestImageConverter:
21+
"""Test cases for the ImageConverter class"""
22+
23+
def test_converter_creation(self):
24+
"""Test that ImageConverter can be instantiated"""
25+
converter = rawtoaces.ImageConverter()
26+
assert converter is not None
27+
assert isinstance(converter, rawtoaces.ImageConverter)
28+
29+
def test_converter_has_settings(self):
30+
"""Test that ImageConverter has a settings attribute"""
31+
converter = rawtoaces.ImageConverter()
32+
assert hasattr(converter, "settings")
33+
assert converter.settings is not None
34+
35+
def test_converter_has_process_image_method(self):
36+
"""Test that ImageConverter has process_image method"""
37+
converter = rawtoaces.ImageConverter()
38+
assert hasattr(converter, "process_image")
39+
assert callable(converter.process_image)
40+
41+
def test_process_image_with_invalid_path(self):
42+
"""Test process_image with non-existent file returns False"""
43+
import os
44+
converter = rawtoaces.ImageConverter()
45+
46+
# Use a simple invalid filename (no path separators to avoid OS-specific issues)
47+
invalid_path = "nonexistent_file.txt"
48+
49+
# Test with invalid path
50+
try:
51+
result = converter.process_image(invalid_path)
52+
assert result is False
53+
except Exception:
54+
# If an exception is thrown, that's also acceptable behavior for invalid input
55+
pass
56+
57+
# Test with empty path
58+
try:
59+
result = converter.process_image("")
60+
assert result is False
61+
except Exception:
62+
# If an exception is thrown, that's also acceptable behavior for invalid input
63+
pass
64+
65+
66+
class TestSettings:
67+
"""Test cases for the ImageConverter.Settings class"""
68+
69+
def test_settings_creation(self):
70+
"""Test that Settings can be instantiated"""
71+
converter = rawtoaces.ImageConverter()
72+
settings = converter.settings
73+
assert settings is not None
74+
assert isinstance(settings, rawtoaces.ImageConverter.Settings)
75+
76+
def test_settings_direct_creation(self):
77+
"""Test that Settings can be created directly"""
78+
settings = rawtoaces.ImageConverter.Settings()
79+
assert settings is not None
80+
assert isinstance(settings, rawtoaces.ImageConverter.Settings)
81+
82+
83+
if __name__ == "__main__":
84+
pytest.main([__file__])

0 commit comments

Comments
 (0)