Files
cmake/Tests/RunCMake/Instrumentation/json.cmake
T
Daksh Mamodiya 38e0434c9a Instrumentation: Write cmakeInstall snippet on interrupted install
With instrumentation enabled, interrupting `cmake --install` (Ctrl+C)
terminated the process before its overall `cmakeInstall` envelope
snippet was written, orphaning the per-script `install` snippets.

Extend the `cmake --build` interrupt handling to the install site: wrap
the `cmakeInstall` command in `HandleInterrupt`, skip the post-install
hook, and re-raise so the exit status reflects the signal.  Add
cooperative cancellation so the command unwinds -- serial-loop and
parallel `queueScripts` guards stop launching further scripts, and the
parallel failure aggregation is scoped to dispatched runners so
un-launched scripts are not counted as failed.  Force a non-zero result
on interrupt (Windows cannot re-raise), and write the envelope
atomically so a second Ctrl+C cannot truncate it.

Issue: #27859
2026-07-09 11:42:51 -04:00

101 lines
3.7 KiB
CMake

include_guard()
# Read the JSON `filename` into `outvar`.
function(read_json filename outvar)
file(READ "${filename}" ${outvar})
return(PROPAGATE ${outvar})
endfunction()
# Utility for error messages.
function(add_error error)
string(APPEND RunCMake_TEST_FAILED " ${error}\n")
string(APPEND ERROR_MESSAGE " ${error}\n")
return(PROPAGATE RunCMake_TEST_FAILED ERROR_MESSAGE)
endfunction()
# Utility for JSON-specific error messages.
function(json_error file error)
add_error("Error in JSON file ${file}:\n${error}")
return(PROPAGATE RunCMake_TEST_FAILED ERROR_MESSAGE)
endfunction()
# Check if the JSON string `json` has `key` when it's not
# UNEXPECTED. If so, return it in `key`.
function(json_has_key file json key)
cmake_parse_arguments(ARG "UNEXPECTED" "" "" ${ARGN})
unset(missingKey)
string(JSON ${key} ERROR_VARIABLE missingKey GET "${json}" ${key})
if (NOT ARG_UNEXPECTED AND missingKey)
json_error("${file}" "Missing key \'${key}\':\n${json}")
elseif (ARG_UNEXPECTED AND NOT missingKey)
json_error("${file}" "\nUnexpected key \'${key}\':\n${json}")
endif()
return(PROPAGATE RunCMake_TEST_FAILED ERROR_MESSAGE ${key})
endfunction()
# Check if the JSON string `json` does not have `key`.
function(json_missing_key file json key)
string(JSON data ERROR_VARIABLE missingKey GET "${json}" ${key})
if (NOT missingKey)
json_error("${file}" "Has unexpected ${key}.")
endif()
return(PROPAGATE RunCMake_TEST_FAILED ERROR_MESSAGE)
endfunction()
# Check if the JSON string `json` has `key` and its value matches `expected`.
# If successful, return its value in `key`.
function(json_assert_key file json key expected)
string(JSON data ERROR_VARIABLE missingKey GET "${json}" ${key})
if (missingKey)
json_error("${file}" "Missing ${key}.")
endif()
if (NOT ${data} MATCHES ${expected})
json_error(
"${file}"
"Unexpected data in custom content file:\nGot ${data}, Expected ${expected}."
)
endif()
set(${key} ${data} PARENT_SCOPE)
return(PROPAGATE RunCMake_TEST_FAILED ERROR_MESSAGE)
endfunction()
# Check if the two given JSON files are equal.
function(json_equals expected_file actual_file)
read_json("${expected_file}" expected_contents)
read_json("${actual_file}" actual_contents)
string(JSON equal EQUAL ${expected_contents} ${actual_contents})
if (NOT equal)
add_error(
"JSON ${expected_file} does not equal ${actual_file}."
)
endif()
return(PROPAGATE RunCMake_TEST_FAILED ERROR_MESSAGE)
endfunction()
# Verify the aftermath of an interrupted top-level command whose overall
# instrumentation snippet has role `role` and whose post-command hook is `hook`.
# Exactly one such snippet must be present and marked with the interrupting
# signal (any snippet from an earlier uninterrupted warm-up run was collated and
# removed by its hook). The hook must have been skipped entirely, so its
# callback (hook.cmake writes a `${hook}.hook` marker whenever it runs, and any
# earlier copy was removed before the interrupted run) must not have run.
function(check_interrupted_snippet role hook)
file(GLOB snippets LIST_DIRECTORIES false ${v1}/data/${role}-*.json)
list(LENGTH snippets num)
if (NOT num EQUAL 1)
add_error("Expected exactly one ${role} snippet, found ${num}: ${snippets}")
else()
read_json("${snippets}" contents)
string(JSON interruptSignal ERROR_VARIABLE noSignal GET "${contents}" interruptSignal)
if (noSignal OR NOT interruptSignal MATCHES "^[1-9][0-9]*$")
add_error("${role} snippet is not marked interrupted:\n${contents}")
endif()
endif()
if (EXISTS ${v1}/${hook}.hook)
add_error("${hook} hook should be skipped on interrupt, but it ran")
endif()
return(PROPAGATE RunCMake_TEST_FAILED ERROR_MESSAGE)
endfunction()