From e6b3ce22281ef22d3988da487056e34e3f7a7957 Mon Sep 17 00:00:00 2001 From: Ian Lumsden Date: Mon, 15 Jul 2024 21:59:17 -0700 Subject: [PATCH] Adds unit tests for Python bindings --- bindings/python/instrumentation.cpp | 36 ++++++- bindings/python/instrumentation.h | 7 ++ bindings/python/mod.cpp | 9 +- bindings/python/pycaliper/__init__.py | 1 + bindings/python/pycaliper/types.py | 26 ----- test/ci_app_tests/CMakeLists.txt | 25 +++++ test/ci_app_tests/ci_test_py_ann.py | 74 ++++++++++++++ test/ci_app_tests/test_python_api.py | 137 ++++++++++++++++++++++++++ 8 files changed, 284 insertions(+), 31 deletions(-) create mode 100644 test/ci_app_tests/ci_test_py_ann.py create mode 100644 test/ci_app_tests/test_python_api.py diff --git a/bindings/python/instrumentation.cpp b/bindings/python/instrumentation.cpp index f8d579f44..32298d3e4 100644 --- a/bindings/python/instrumentation.cpp +++ b/bindings/python/instrumentation.cpp @@ -1,4 +1,5 @@ #include "instrumentation.h" +#include "variant.h" #include namespace cali { @@ -18,6 +19,31 @@ PythonAttribute::PythonAttribute(const char *name, cali_attr_type type, } } +PythonAttribute::PythonAttribute(const char *name, cali_attr_type type, + cali_attr_properties opt, + std::vector &meta_attrs, + std::vector &meta_vals) { + if (meta_attrs.size() != meta_vals.size()) { + throw std::runtime_error( + "'meta_attrs' and 'meta_vals' must be same length"); + } + size_t num_meta_elems = meta_attrs.size(); + cali_id_t *meta_attr_list = new cali_id_t[num_meta_elems]; + cali_variant_t *meta_val_list = new cali_variant_t[num_meta_elems]; + for (size_t i = 0; i < num_meta_elems; i++) { + meta_attr_list[i] = meta_attrs[i].m_attr_id; + meta_val_list[i] = meta_vals[i].c_variant; + } + m_attr_id = cali_create_attribute_with_metadata( + name, type, static_cast(properties), num_meta_elems, meta_attr_list, + meta_val_list); + if (m_attr_id == CALI_INV_ID) { + throw std::runtime_error("Could not create attribute with metadata"); + } + delete[] meta_val_list; + delete[] meta_attr_list; +} + PythonAttribute::PythonAttribute(cali_id_t id) { if (id == CALI_INV_ID) { throw std::runtime_error("Invalid attribute"); @@ -66,12 +92,16 @@ void create_caliper_instrumentation_mod( // PythonAttribute bindings py::class_ cali_attribute_type(caliper_instrumentation_mod, "Attribute"); - cali_attribute_type.def( - py::init(), "", - py::arg(), py::arg()); + cali_attribute_type.def(py::init(), "", + py::arg(), py::arg()); cali_attribute_type.def( py::init(), "", py::arg(), py::arg(), py::arg("opt")); + cali_attribute_type.def( + py::init &, + std::vector &>(), + ""); cali_attribute_type.def_static("find_attribute", &PythonAttribute::find_attribute); cali_attribute_type.def_property_readonly("name", &PythonAttribute::name); diff --git a/bindings/python/instrumentation.h b/bindings/python/instrumentation.h index 461f1cdbe..00d929ec7 100644 --- a/bindings/python/instrumentation.h +++ b/bindings/python/instrumentation.h @@ -3,6 +3,8 @@ #include "common.h" +#include "variant.h" + namespace cali { class PythonAttribute { @@ -12,6 +14,11 @@ class PythonAttribute { PythonAttribute(const char *name, cali_attr_type type, cali_attr_properties opt); + PythonAttribute(const char *name, cali_attr_type type, + cali_attr_properties opt, + std::vector &meta_attrs, + std::vector &meta_vals); + static PythonAttribute find_attribute(const char *name); const char *name() const; diff --git a/bindings/python/mod.cpp b/bindings/python/mod.cpp index 1d68200d8..ec46d56f0 100644 --- a/bindings/python/mod.cpp +++ b/bindings/python/mod.cpp @@ -11,12 +11,17 @@ bool pycaliper_is_initialized() { return cali_is_initialized() != 0; } PYBIND11_MODULE(__pycaliper_impl, m) { m.attr("__version__") = cali_caliper_version(); + m.def("config_preset", [](std::map &preset_map) { + for (auto kv : preset_map) { + cali_config_preset(kv.first, kv.second); + } + }); m.def("init", &cali_init); m.def("is_initialized", &pycaliper_is_initialized); auto types_mod = m.def_submodule("types"); - py::enum_ c_attr_type(types_mod, "AttrTypeEnum"); + py::enum_ c_attr_type(types_mod, "AttrType"); c_attr_type.value("CALI_TYPE_INV", CALI_TYPE_INV); c_attr_type.value("CALI_TYPE_USR", CALI_TYPE_USR); c_attr_type.value("CALI_TYPE_INT", CALI_TYPE_INT); @@ -30,7 +35,7 @@ PYBIND11_MODULE(__pycaliper_impl, m) { c_attr_type.export_values(); py::enum_ c_attr_properties(types_mod, - "AttrPropertiesEnum"); + "AttrProperties"); c_attr_properties.value("CALI_ATTR_DEFAULT", CALI_ATTR_DEFAULT); c_attr_properties.value("CALI_ATTR_ASVALUE", CALI_ATTR_ASVALUE); c_attr_properties.value("CALI_ATTR_NOMERGE", CALI_ATTR_NOMERGE); diff --git a/bindings/python/pycaliper/__init__.py b/bindings/python/pycaliper/__init__.py index 3e271b2f1..63bf6f5ff 100644 --- a/bindings/python/pycaliper/__init__.py +++ b/bindings/python/pycaliper/__init__.py @@ -1,5 +1,6 @@ from pycaliper.__pycaliper_impl import ( __version__, + config_preset, init, is_initialized, ) diff --git a/bindings/python/pycaliper/types.py b/bindings/python/pycaliper/types.py index 8426b863c..64b836fd6 100644 --- a/bindings/python/pycaliper/types.py +++ b/bindings/python/pycaliper/types.py @@ -1,27 +1 @@ from pycaliper.__pycaliper_impl.types import * - -from enum import Enum - - -_CALI_TYPE_ENUM_PREFIX = "CALI_TYPE_" -_CALI_PROPERTIES_ENUM_PREFIX = "CALI_ATTR_" - - -AttrType = Enum( - "AttrType", - { - name[len(_CALI_TYPE_ENUM_PREFIX) :]: val - for name, val in AttrTypeEnum.__members__.items() - if name.startswith(_CALI_TYPE_ENUM_PREFIX) - }, -) - - -AttrProperties = Enum( - "AttrProperties", - { - name[len(_CALI_PROPERTIES_ENUM_PREFIX) :]: val - for name, val in AttrPropertiesEnum.__members__.items() - if name.startswith(_CALI_PROPERTIES_ENUM_PREFIX) - }, -) diff --git a/test/ci_app_tests/CMakeLists.txt b/test/ci_app_tests/CMakeLists.txt index d9d99571b..a3f821473 100644 --- a/test/ci_app_tests/CMakeLists.txt +++ b/test/ci_app_tests/CMakeLists.txt @@ -24,6 +24,9 @@ set(CALIPER_CI_MPI_TEST_APPS ci_test_mpi_channel_manager) set(CALIPER_CI_Fortran_TEST_APPS ci_test_f_ann) +set(CALIPER_CI_Python_TEST_APPS + ci_test_py_ann.py +) foreach(app ${CALIPER_CI_CXX_TEST_APPS}) add_executable(${app} ${app}.cpp) @@ -130,6 +133,28 @@ if (WITH_FORTRAN) list(APPEND PYTHON_SCRIPTS test_fortran_api.py) endif() +if (WITH_PYTHON_BINDINGS) + foreach(file ${CALIPER_CI_Python_TEST_APPS}) + add_custom_target(${file} ALL + COMMAND ${CMAKE_COMMAND} -E create_symlink + ${CMAKE_CURRENT_SOURCE_DIR}/${file} + ${CMAKE_CURRENT_BINARY_DIR}/${file}) + endforeach() + + list(APPEND PYTHON_SCRIPTS test_python_api.py) +endif() + +if (WITH_PYTHON_BINDINGS) + foreach(file ${CALIPER_CI_Python_TEST_APPS}) + add_custom_target(${file} ALL + COMMAND ${CMAKE_COMMAND} -E create_symlink + ${CMAKE_CURRENT_SOURCE_DIR}/${file} + ${CMAKE_CURRENT_BINARY_DIR}/${file}) + endforeach() + + list(APPEND PYTHON_SCRIPTS test_python_api.py) +endif() + set(DATA_FILES example_node_info.json) diff --git a/test/ci_app_tests/ci_test_py_ann.py b/test/ci_app_tests/ci_test_py_ann.py new file mode 100644 index 000000000..017f94b30 --- /dev/null +++ b/test/ci_app_tests/ci_test_py_ann.py @@ -0,0 +1,74 @@ +# --- Caliper continuous integration test app for Python annotation interface + +from pycaliper import config_preset +from pycaliper.instrumentation import ( + Attribute, + set_global_byname, + begin_byname, + set_byname, + end_byname, +) +from pycaliper.types import AttrProperties +from pycaliper.variant import Variant +from pycaliper.config_manager import ConfigManager + +import sys + + +def main(): + config_preset({"CALI_CHANNEL_FLUSH_ON_EXIT", "false"}) + + mgr = ConfigManager() + if len(sys.argv) > 1: + mgr.add(sys.argv[1]) + + if mgr.error(): + print("Caliper config error:", mgr.err_msg(), file=sys.stderr) + exit(-1) + + mgr.start() + + set_global_byname("global.double", 42.42) + set_global_byname("global.int", 1337) + set_global_byname("global.string", "my global string") + set_global_byname("global.uint", 42) + + iter_attr = Attribute("iteration", AttrProperties.CALI_ATTR_ASVALUE) + + begin_byname("phase", "loop") + + for i in range(4): + iter_attr.begin(i) + iter_attr.end() + + end_byname("phase") + + begin_byname("ci_test_c_ann.meta-attr") + + meta_attr = Attribute("meta-attr") + meta_val = Variant(47) + + test_attr = Attribute( + "test-attr-with-metadata", + AttrProperties.CALI_ATTR_UNLIGNED, + [meta_attr], + [meta_val], + ) + + test_attr.set("abracadabra") + + end_byname("ci_test_c_ann.meta-attr") + + begin_byname("ci_test_c_ann.setbyname") + + set_byname("attr.int", 20) + set_byname("attr.dbl", 1.25) + set_byname("attr.str", "fidibus") + + end_byname("ci_test_c_ann.setbyname") + + mgr.flush() + + +if __name__ == "__main__": + main() diff --git a/test/ci_app_tests/test_python_api.py b/test/ci_app_tests/test_python_api.py new file mode 100644 index 000000000..bbc598114 --- /dev/null +++ b/test/ci_app_tests/test_python_api.py @@ -0,0 +1,137 @@ +# Tests of the Python API + +import sys + +import unittest + +import calipertest as cat + + +class CaliperPythonAPITest(unittest.TestCase): + """Caliper Python API test cases""" + + def test_py_ann_trace(self): + target_cmd = [ + sys.executable, + "./ci_test_py_ann.py", + "event-trace,output=stdout", + ] + query_cmd = ["../../src/tools/cali-query/cali-query", "-e"] + + caliper_config = {"CALI_LOG_VERBOSITY": "0"} + + query_output = cat.run_test_with_query(target_cmd, query_cmd, caliper_config) + snapshots = cat.get_snapshots_from_text(query_output) + + self.assertTrue(len(snapshots) >= 10) + + self.assertTrue( + cat.has_snapshot_with_keys( + snapshots, {"iteration", "phase", "time.duration.ns", "global.int"} + ) + ) + self.assertTrue( + cat.has_snapshot_with_attributes( + snapshots, {"event.end#phase": "loop", "phase": "loop"} + ) + ) + self.assertTrue( + cat.has_snapshot_with_attributes( + snapshots, + {"event.end#iteration": "3", "iteration": "3", "phase": "loop"}, + ) + ) + self.assertTrue( + cat.has_snapshot_with_keys( + snapshots, + {"attr.int", "attr.dbl", "attr.str", "ci_test_c_ann.setbyname"}, + ) + ) + self.assertTrue( + cat.has_snapshot_with_attributes( + snapshots, {"attr.int": "20", "attr.str": "fidibus"} + ) + ) + self.assertTrue( + cat.has_snapshot_with_attributes( + snapshots, {"test-attr-with-metadata": "abracadabra"} + ) + ) + + def test_py_ann_globals(self): + target_cmd = [sys.executable, "./ci_test_py_ann.py"] + query_cmd = ["../../src/tools/cali-query/cali-query", "-e", "--list-globals"] + + caliper_config = { + "CALI_CONFIG_PROFILE": "serial-trace", + "CALI_RECORDER_FILENAME": "stdout", + "CALI_LOG_VERBOSITY": "0", + } + + query_output = cat.run_test_with_query(target_cmd, query_cmd, caliper_config) + snapshots = cat.get_snapshots_from_text(query_output) + + self.assertTrue(len(snapshots) == 1) + + self.assertTrue( + cat.has_snapshot_with_keys( + snapshots, + { + "global.double", + "global.string", + "global.int", + "global.uint", + "cali.caliper.version", + }, + ) + ) + self.assertTrue( + cat.has_snapshot_with_attributes( + snapshots, + { + "global.int": "1337", + "global.string": "my global string", + "global.uint": "42", + }, + ) + ) + + def test_py_ann_metadata(self): + target_cmd = [sys.executable, "./ci_test_py_ann.py"] + query_cmd = [ + "../../src/tools/cali-query/cali-query", + "-e", + "--list-attributes", + "--print-attributes", + "cali.attribute.name,cali.attribute.type,meta-attr", + ] + + caliper_config = { + "CALI_CONFIG_PROFILE": "serial-trace", + "CALI_RECORDER_FILENAME": "stdout", + "CALI_LOG_VERBOSITY": "0", + } + + query_output = cat.run_test_with_query(target_cmd, query_cmd, caliper_config) + snapshots = cat.get_snapshots_from_text(query_output) + + self.assertTrue( + cat.has_snapshot_with_attributes( + snapshots, + {"cali.attribute.name": "meta-attr", "cali.attribute.type": "int"}, + ) + ) + self.assertTrue( + cat.has_snapshot_with_attributes( + snapshots, + { + "cali.attribute.name": "test-attr-with-metadata", + "cali.attribute.type": "string", + "meta-attr": "47", + }, + ) + ) + + +if __name__ == "__main__": + unittest.main()