Tests: Centralize JSON schema validation

As the number of areas in CMake for which we've provided JSON schemas
has expanded, testing infrastructure has mostly been copy-pasted across
the relevant test suites. Unify it to a single reusable validator
script and CMake helper.
This commit is contained in:
Tyler Yankee
2026-07-23 10:02:40 -04:00
parent af3db9d227
commit 4eda69be82
21 changed files with 160 additions and 226 deletions

View File

@@ -9,8 +9,6 @@ endif()
set(RunCMake-check-file check.cmake)
include("${RunCMake_SOURCE_DIR}/validate_schema.cmake")
function(reset_cmake_presets_directory name)
set(RunCMake_TEST_SOURCE_DIR "${RunCMake_BINARY_DIR}/${name}")
file(REMOVE_RECURSE "${RunCMake_TEST_SOURCE_DIR}")
@@ -130,7 +128,9 @@ endfunction()
# Test CMakePresets.json errors
set(CMakePresets_SCHEMA_EXPECTED_RESULT 1)
run_cmake_presets(NoCMakePresets)
set(CMakePresets_SCHEMA_EXPECTED_RESULT 3)
run_cmake_presets(Comment)
run_cmake_presets(JSONParseError)
set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
run_cmake_presets(NoCommentValid)
run_cmake_presets(CommentValid)
@@ -142,7 +142,6 @@ run_cmake_presets(NoGraphvizValid)
run_cmake_presets(GraphvizValid)
set(CMakePresets_SCHEMA_EXPECTED_RESULT 1)
run_cmake_presets(GraphvizValidOldSchema)
run_cmake_presets(JSONParseError)
run_cmake_presets(InvalidRoot)
run_cmake_presets(NoVersion)
run_cmake_presets(InvalidVersion)

View File

@@ -1,22 +1,40 @@
if(Python_EXECUTABLE AND CMake_TEST_JSON_SCHEMA)
if(NOT CMakePresets_SCHEMA_EXPECTED_RESULT)
set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
endif()
if(EXISTS "${RunCMake_TEST_SOURCE_DIR}/CMakePresets.json")
validate_schema("${RunCMake_TEST_SOURCE_DIR}/CMakePresets.json" "${CMakePresets_SCHEMA_EXPECTED_RESULT}")
endif()
if(NOT CMakeUserPresets_SCHEMA_EXPECTED_RESULT)
set(CMakeUserPresets_SCHEMA_EXPECTED_RESULT 0)
endif()
if(EXISTS "${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json")
validate_schema("${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json" "${CMakeUserPresets_SCHEMA_EXPECTED_RESULT}")
endif()
if(NOT CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
set(CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS "${_CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS}")
endif()
foreach(_f _r IN ZIP_LISTS _CMakePresets_EXTRA_FILES_OUT CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
validate_schema("${_f}" "${_r}")
endforeach()
if(NOT (Python_EXECUTABLE AND CMake_TEST_JSON_SCHEMA))
return()
endif()
include("${RunCMake_SOURCE_DIR}/../validate_json_schema.cmake")
if(NOT CMakePresets_SCHEMA_EXPECTED_RESULT)
set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
endif()
set(CMakePresets_JSON_SCHEMA "${RunCMake_SOURCE_DIR}/../../../Help/manual/presets/schema.json")
if(EXISTS "${RunCMake_TEST_SOURCE_DIR}/CMakePresets.json")
validate_json_schema(
"${CMakePresets_JSON_SCHEMA}"
"${RunCMake_TEST_SOURCE_DIR}/CMakePresets.json"
EXPECTED_RESULT "${CMakePresets_SCHEMA_EXPECTED_RESULT}"
)
endif()
if(NOT CMakeUserPresets_SCHEMA_EXPECTED_RESULT)
set(CMakeUserPresets_SCHEMA_EXPECTED_RESULT 0)
endif()
if(EXISTS "${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json")
validate_json_schema(
"${CMakePresets_JSON_SCHEMA}"
"${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json"
EXPECTED_RESULT "${CMakeUserPresets_SCHEMA_EXPECTED_RESULT}"
)
endif()
if(NOT CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
set(CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS "${_CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS}")
endif()
foreach(_f _r IN ZIP_LISTS _CMakePresets_EXTRA_FILES_OUT CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
validate_json_schema(
"${CMakePresets_JSON_SCHEMA}" "${_f}"
EXPECTED_RESULT "${_r}"
)
endforeach()

View File

@@ -1,17 +0,0 @@
function(validate_schema file expected_result)
if (NOT CMakePresets_VALIDATE_SCRIPT_PATH)
set(CMakePresets_VALIDATE_SCRIPT_PATH "${RunCMake_SOURCE_DIR}/validate_schema.py")
endif()
execute_process(
COMMAND "${Python_EXECUTABLE}" "${CMakePresets_VALIDATE_SCRIPT_PATH}" "${file}"
RESULT_VARIABLE _result
ERROR_VARIABLE _error
)
if(NOT _result STREQUAL expected_result)
string(REPLACE "\n" "\n " _error_p "${_error}")
string(APPEND RunCMake_TEST_FAILED "Expected result of validating ${file}: ${expected_result}\nActual result: ${_result}\nError:\n ${_error_p}")
endif()
set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
endfunction()

View File

@@ -1,20 +0,0 @@
import json
import jsonschema
import os.path
import sys
with open(sys.argv[1], "r", encoding="utf-8-sig") as f:
contents = json.load(f)
schema_file = os.path.join(
os.path.dirname(__file__),
"..", "..", "..", "Help", "manual", "presets", "schema.json")
with open(schema_file, "r", encoding="utf-8") as f:
schema = json.load(f)
try:
jsonschema.validate(contents, schema)
except (jsonschema.ValidationError, jsonschema.SchemaError) as e:
sys.stderr.write(e.message)
sys.exit(1)

View File

@@ -1,3 +1 @@
set(CMakePresets_VALIDATE_SCRIPT_PATH "${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.py")
include("${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.cmake")
include("${RunCMake_SOURCE_DIR}/../CMakePresets/check.cmake")

View File

@@ -1,3 +1 @@
set(CMakePresets_VALIDATE_SCRIPT_PATH "${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.py")
include("${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.cmake")
include("${RunCMake_SOURCE_DIR}/../CMakePresets/check.cmake")

View File

@@ -1,3 +1 @@
set(CMakePresets_VALIDATE_SCRIPT_PATH "${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.py")
include("${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.cmake")
include("${RunCMake_SOURCE_DIR}/../CMakePresets/check.cmake")

View File

@@ -1,3 +1 @@
set(CMakePresets_VALIDATE_SCRIPT_PATH "${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.py")
include("${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.cmake")
include("${RunCMake_SOURCE_DIR}/../CMakePresets/check.cmake")

View File

@@ -512,18 +512,9 @@ function(show_only_json_check_python v)
file(WRITE "${json_file}" "${actual_stdout}")
set(actual_stdout "" PARENT_SCOPE)
if(CMake_TEST_JSON_SCHEMA)
execute_process(
COMMAND ${Python_EXECUTABLE} "${RunCMake_SOURCE_DIR}/show-only_json_validate_schema.py" "${json_file}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
)
if(NOT result STREQUAL 0)
string(REPLACE "\n" "\n " output "${output}")
string(APPEND RunCMake_TEST_FAILED "Failed to validate version ${v} JSON schema for file: ${file}\nOutput:\n${output}\n")
endif()
endif()
include("${RunCMake_SOURCE_DIR}/../validate_json_schema.cmake")
set(schema_file "${RunCMake_SOURCE_DIR}/../../../Help/manual/ctest/show-only-schema.json")
validate_json_schema("${schema_file}" "${json_file}")
execute_process(
COMMAND ${Python_EXECUTABLE} "${RunCMake_SOURCE_DIR}/show-only_json-v${v}_check.py" "${json_file}"

View File

@@ -1,16 +0,0 @@
import json
import jsonschema
import os.path
import sys
with open(sys.argv[1], "r", encoding="utf-8-sig") as f:
contents = json.load(f)
schema_file = os.path.join(
os.path.dirname(__file__),
"..", "..", "..", "Help", "manual", "ctest", "show-only-schema.json")
with open(schema_file, "r", encoding="utf-8") as f:
schema = json.load(f)
jsonschema.validate(contents, schema)

View File

@@ -14,16 +14,10 @@ function(version_json_check_python v is_json_ready)
set(actual_stdout "" PARENT_SCOPE)
endif()
execute_process(
COMMAND ${Python_EXECUTABLE} "${RunCMake_SOURCE_DIR}/version_json_validate_schema.py" "${json_file}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
)
if(NOT result STREQUAL 0)
string(REPLACE "\n" "\n " output "${output}")
string(APPEND RunCMake_TEST_FAILED "Failed to validate version ${v} JSON schema for file: ${json_file}\nOutput:\n${output}\n")
endif()
include("${RunCMake_SOURCE_DIR}/../validate_json_schema.cmake")
set(schema_file "${RunCMake_SOURCE_DIR}/../../../Help/manual/cmake/version-schema.json")
validate_json_schema("${schema_file}" "${json_file}")
return(PROPAGATE RunCMake_TEST_FAILED)
endfunction()

View File

@@ -1,16 +0,0 @@
import json
import jsonschema
import os.path
import sys
with open(sys.argv[1], "r", encoding="utf-8-sig") as f:
contents = json.load(f)
schema_file = os.path.join(
os.path.dirname(__file__),
"..", "..", "..", "Help", "manual", "cmake", "version-schema.json")
with open(schema_file, "r", encoding="utf-8") as f:
schema = json.load(f)
jsonschema.validate(contents, schema)

View File

@@ -2,31 +2,7 @@ include(RunCMake)
cmake_policy(SET CMP0140 NEW)
# Arguments after the first are the files to validate against the schema
function(validate_fileapi_schema schema)
if(NOT ARGN)
# No files to validate against the schema
return()
endif()
list(JOIN ARGN "\n" file_list)
set(file_list_file ${RunCMake_TEST_BINARY_DIR}/check_file_list.txt)
file(WRITE "${file_list_file}" "${file_list}")
execute_process(
COMMAND ${Python_EXECUTABLE}
"${RunCMake_SOURCE_DIR}/fileapi_validate_schema.py"
"${file_list_file}"
"${schema}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
)
if(NOT result STREQUAL 0)
string(REPLACE "\n" "\n " output "${output}")
string(APPEND RunCMake_TEST_FAILED
"Failed to validate files against JSON schema: ${schema}\nOutput:\n${output}\n")
endif()
return(PROPAGATE RunCMake_TEST_FAILED)
endfunction()
include("${RunCMake_SOURCE_DIR}/../validate_json_schema.cmake")
# Function called in *-check.cmake scripts to check api files.
function(check_api expect)
@@ -73,9 +49,13 @@ in directory:
endif()
endforeach()
foreach(schema_type IN LISTS schema_types)
validate_fileapi_schema(
${schema_dir}/schema_${schema_type}.json
${schema_type_${schema_type}}
if("${schema_type_${schema_type}}" STREQUAL "")
# No files to validate against the schema
continue()
endif()
validate_json_schema(
"${schema_dir}/schema_${schema_type}.json"
"${schema_type_${schema_type}}"
)
endforeach()
endif()
@@ -95,9 +75,9 @@ function(check_stateful_queries)
REPLACE "^(.+)$" "${prefix}-\\1/query.json"
OUTPUT_VARIABLE query_json_files
)
validate_fileapi_schema(
${schema_dir}/schema_stateful_query.json
${query_json_files}
validate_json_schema(
"${schema_dir}/schema_stateful_query.json"
"${query_json_files}"
)
return(PROPAGATE RunCMake_TEST_FAILED)
endfunction()

View File

@@ -1,23 +0,0 @@
import json
import jsonschema
import sys
# First argument is a file containing the list of files to check
with open(sys.argv[1], "r", encoding="utf-8") as file_list:
files_to_check = [line.strip() for line in file_list if line.strip()]
# Second argument is the schema file
with open(sys.argv[2], "r", encoding="utf-8-sig") as f:
schema = json.load(f)
# Check each file against the schema
for file_path in files_to_check:
try:
with open(file_path, "r", encoding="utf-8-sig") as f:
contents = json.load(f)
# The following raises an exception if validation fails
jsonschema.validate(contents, schema)
except Exception as e:
print(f"Failed to validate file {file_path}: {e}")
raise

View File

@@ -1,13 +1,14 @@
include(${CMAKE_CURRENT_LIST_DIR}/verify-snippet.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/json.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/validate_schema.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../validate_json_schema.cmake)
set(schema_file "${CMAKE_CURRENT_LIST_DIR}/../../../Help/manual/instrumentation/query-v1-schema.json")
file(GLOB_RECURSE queries LIST_DIRECTORIES false ${v1}/query/*)
foreach(query ${queries})
validate_schema(
"${query}"
"${CMAKE_CURRENT_LIST_DIR}/../../../Help/manual/instrumentation/query-v1-schema.json"
"${schema_validate_result}"
validate_json_schema(
"${schema_file}" "${query}"
EXPECTED_RESULT "${schema_validate_result}"
)
if (RunCMake_TEST_FAILED)
add_error("${RunCMake_TEST_FAILED}")

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.30)
include(${CMAKE_CURRENT_LIST_DIR}/json.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/validate_schema.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../validate_json_schema.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/verify-snippet.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/verify-trace.cmake)
@@ -53,12 +53,8 @@ function(add_error error)
return(PROPAGATE ERROR_MESSAGE)
endfunction()
validate_schema(
"${index}"
"${CMAKE_CURRENT_LIST_DIR}/../../../Help/manual/instrumentation/index-v1-schema.json"
# We expect to always generate valid index files.
0
)
set(index_schema "${CMAKE_CURRENT_LIST_DIR}/../../../Help/manual/instrumentation/index-v1-schema.json")
validate_json_schema("${index_schema}" "${index}")
if (RunCMake_TEST_FAILED)
add_error("${RunCMake_TEST_FAILED}")
unset(RunCMake_TEST_FAILED)

View File

@@ -1,15 +0,0 @@
function(validate_schema json_file schema_file expected_result)
if (Python_EXECUTABLE AND CMake_TEST_JSON_SCHEMA)
execute_process(
COMMAND ${Python_EXECUTABLE} "${CMAKE_CURRENT_LIST_DIR}/validate_schema.py" "${json_file}" "${schema_file}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
)
if(NOT result MATCHES ${expected_result})
string(REPLACE "\n" "\n " output "${output}")
string(APPEND RunCMake_TEST_FAILED "Failed to validate version JSON schema for file: ${json_file}\nOutput:\n${output}\nResult: ${result}\nExpected: ${expected_result}")
endif()
return(PROPAGATE RunCMake_TEST_FAILED)
endif()
endfunction()

View File

@@ -1,15 +0,0 @@
import json
import jsonschema
import sys
with open(sys.argv[1], "r", encoding="utf-8-sig") as f:
contents = json.load(f)
with open(sys.argv[2], "r", encoding="utf-8") as f:
schema = json.load(f)
try:
jsonschema.validate(contents, schema)
except jsonschema.ValidationError as e:
print(e)
sys.exit(1)

View File

@@ -2,7 +2,7 @@
include_guard()
include(${CMAKE_CURRENT_LIST_DIR}/json.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/validate_schema.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/../validate_json_schema.cmake)
function(snippet_has_fields snippet contents)
get_filename_component(filename "${snippet}" NAME)
@@ -111,12 +111,8 @@ function(verify_snippet_file snippet contents)
json_error("${snippet}" "Role \"${role}\" doesn't match snippet filename")
endif()
validate_schema(
"${snippet}"
"${CMAKE_CURRENT_LIST_DIR}/../../../Help/manual/instrumentation/snippet-v1-schema.json"
# We expect to always generate valid snippet files.
0
)
set(snippet_schema "${CMAKE_CURRENT_LIST_DIR}/../../../Help/manual/instrumentation/snippet-v1-schema.json")
validate_json_schema("${snippet_schema}" "${snippet}")
if (RunCMake_TEST_FAILED)
add_error("${RunCMake_TEST_FAILED}")
endif()

View File

@@ -0,0 +1,35 @@
cmake_policy(SET CMP0140 NEW)
function(validate_json_schema schema_file input_files)
if (NOT(Python_EXECUTABLE AND CMake_TEST_JSON_SCHEMA))
return()
endif()
cmake_parse_arguments(ARG_VS "" "EXPECTED_RESULT" "" ${ARGN})
if(NOT ARG_VS_EXPECTED_RESULT)
set(ARG_VS_EXPECTED_RESULT 0)
endif()
set(_validate_py "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/validate_json_schema.py")
cmake_path(SET _schema NORMALIZE "${schema_file}")
set(_inputs)
foreach(_input IN LISTS input_files)
cmake_path(SET _input NORMALIZE "${_input}")
list(APPEND _inputs "${_input}")
endforeach()
execute_process(
COMMAND
"${Python_EXECUTABLE}" "${_validate_py}" "${_schema}"
"--input-files" ${_inputs}
# Include "--verbose" here for local debugging as needed.
RESULT_VARIABLE result
ERROR_VARIABLE error
)
if(NOT result MATCHES "${ARG_VS_EXPECTED_RESULT}")
string(REPLACE "\n" "\n " error "${error}")
string(APPEND RunCMake_TEST_FAILED "Failed to validate version JSON schema for file: ${json_file}\nOutput:\n${error}\nResult: ${result}\nExpected: ${ARG_VS_EXPECTED_RESULT}")
endif()
return(PROPAGATE RunCMake_TEST_FAILED)
endfunction()

View File

@@ -0,0 +1,54 @@
import argparse
import json
import pathlib
import sys
import jsonschema
def main(argv=None) -> int:
parser = argparse.ArgumentParser(description="Validate CMake JSON schemas")
parser.add_argument(
"schema",
type=pathlib.Path,
help="the path to the schema file",
)
parser.add_argument(
"--input-files",
nargs="+",
type=pathlib.Path,
help="the path to an input file to validate",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="enable verbose output",
)
args = parser.parse_args(argv)
with open(args.schema, "r", encoding="utf-8") as f:
schema = json.load(f)
for file in args.input_files:
try:
with open(file, "r", encoding="utf-8-sig") as f:
contents = json.load(f)
jsonschema.validate(contents, schema)
except jsonschema.ValidationError as e:
message = str(e) if args.verbose else str(e.message)
print(f"Failed to validate file {file}: {message}", file=sys.stderr)
return 1
except jsonschema.SchemaError as e:
message = str(e) if args.verbose else str(e.message)
print(f"Failed to validate schema: {message}", file=sys.stderr)
return 2
except Exception as e:
print(f"Unknown exception: {e}", file=sys.stderr)
return 3
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))