diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 22f52e0..c7545df 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -144,7 +144,9 @@ Known local toolchain state: covered by `pano_cli_save_project_roundtrip_smoke` and `pano_cli_save_project_payload_roundtrip_smoke`, which reload generated metadata-only and targeted dirty-face-payload projects through - `pano_cli load-project`. + `pano_cli load-project`, plus + `pano_cli_save_project_rejects_non_finite_opacity`, which verifies rejected + automation floats do not create output files. - `pano_cli create-document` supports `--frames` and `--frame-duration-ms` and is covered by `pano_cli_create_animation_document_smoke`. - `pano_cli simulate-document-edits` exercises pure document layer/frame edit diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 43cd782..32e7aa5 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -354,7 +354,8 @@ payloads are present. visibility, opacity, blend mode, alpha lock, per-layer frame durations, and dirty-face payloads targeted to layer/frame/face slots. `pano_cli save-project` exposes the generated writer for metadata-only and test dirty-face-payload -round-trips through `load-project`. +round-trips through `load-project` and rejects non-finite automation float +inputs before writing files. `pp_document::export_ppi_project_document` converts pure documents into PPI bytes using that writer, including PNG-encoded layer/frame face payloads. `pano_cli simulate-document-export` exercises that pure document export path, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4ca4529..f4bd9d4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -329,6 +329,15 @@ if(TARGET pano_cli) set_tests_properties(pano_cli_save_project_payload_roundtrip_smoke PROPERTIES LABELS "assets;document;integration;desktop-fast") + add_test(NAME pano_cli_save_project_rejects_non_finite_opacity + COMMAND "${CMAKE_COMMAND}" + -DPANO_CLI=$ + -DOUTPUT_PATH=${CMAKE_CURRENT_BINARY_DIR}/data/generated/rejected-non-finite-opacity.ppi + "-DEXPECTED_OUTPUT=floating-point value must be finite" + -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/expect_pano_cli_save_project_failure.cmake") + set_tests_properties(pano_cli_save_project_rejects_non_finite_opacity PROPERTIES + LABELS "assets;document;integration;desktop-fast;fuzz") + add_test(NAME pano_cli_save_document_project_roundtrip_smoke COMMAND "${CMAKE_COMMAND}" -DPANO_CLI=$ diff --git a/tests/cmake/expect_pano_cli_save_project_failure.cmake b/tests/cmake/expect_pano_cli_save_project_failure.cmake new file mode 100644 index 0000000..2443d76 --- /dev/null +++ b/tests/cmake/expect_pano_cli_save_project_failure.cmake @@ -0,0 +1,36 @@ +if(NOT DEFINED PANO_CLI) + message(FATAL_ERROR "PANO_CLI is required") +endif() + +if(NOT DEFINED OUTPUT_PATH) + message(FATAL_ERROR "OUTPUT_PATH is required") +endif() + +if(NOT DEFINED EXPECTED_OUTPUT) + message(FATAL_ERROR "EXPECTED_OUTPUT is required") +endif() + +file(REMOVE "${OUTPUT_PATH}") + +execute_process( + COMMAND "${PANO_CLI}" save-project + --path "${OUTPUT_PATH}" + --width 64 + --height 32 + --layer-opacity nan + RESULT_VARIABLE save_result + OUTPUT_VARIABLE save_output + ERROR_VARIABLE save_error) + +if(save_result EQUAL 0) + message(FATAL_ERROR "save-project unexpectedly succeeded: ${save_output}${save_error}") +endif() + +string(FIND "${save_output}${save_error}" "${EXPECTED_OUTPUT}" expected_index) +if(expected_index EQUAL -1) + message(FATAL_ERROR "save-project failure did not contain expected output: ${save_output}${save_error}") +endif() + +if(EXISTS "${OUTPUT_PATH}") + message(FATAL_ERROR "save-project created output despite rejected inputs: ${OUTPUT_PATH}") +endif() diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 5b946b0..e9878a1 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -220,6 +220,11 @@ pp::foundation::Result parse_float_arg(std::string_view text) pp::foundation::Status::invalid_argument("invalid floating-point value")); } + if (!std::isfinite(value)) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument("floating-point value must be finite")); + } + return pp::foundation::Result::success(value); }