44 *
55 * SPDX-License-Identifier: Apache-2.0
66 */
7+ #include < filesystem>
8+ #include < optional>
9+ #include < set>
10+ #include < vector>
711
8- #include " wrapper.hpp"
9-
10- #include " codegen/codegen_naming.hpp"
11- #include " pybind/pyembed.hpp"
12+ // 3rd party headers
1213#include < fmt/format.h>
13- #include < optional>
1414#include < pybind11/embed.h>
1515#include < pybind11/stl.h>
1616
17- #include < set>
18- #include < vector>
17+ // NMODL headers
18+ #include " codegen/codegen_naming.hpp"
19+ #include " pybind/ode_py.hpp"
20+ #include " pybind/wrapper.hpp"
21+ #include " pybind/pyembed.hpp"
22+ #include " utils/common_utils.hpp"
1923
20- #include " ode_py.hpp"
2124
25+ namespace fs = std::filesystem;
2226namespace py = pybind11;
2327using namespace py ::literals;
2428
2529namespace nmodl {
2630namespace pybind_wrappers {
2731
32+ // This wrapper is used for obtaining better coverage in `ode.py`.
33+ // Since we embed the `ode.py` as a string, there is no way to check what was covered via running
34+ // pytest or similar. Instead, we use the coverage.py API directly.
35+ static void run_python_script (const std::string& script, const py::dict& locals) {
36+ #ifdef NRN_ENABLE_COVERAGE
37+ // to prevent race conditions during testing, we generate a random suffix
38+ const auto & suffix =
39+ nmodl::utils::generate_random_string (20 , nmodl::utils::UseNumbersInString::WithoutNumbers);
40+
41+ py::exec (fmt::format (R"(
42+ import coverage
43+ cov = coverage.Coverage(data_suffix='{}')
44+ cov.start()
45+ )" ,
46+ suffix),
47+ locals);
48+ const auto & code_with_mapping = std::string (" exec(compile(r'''" + ode_py + script + " ''', '" +
49+ ode_py_path + " ', 'exec'))" );
50+ py::exec (code_with_mapping, locals);
51+ #else
52+ py::exec (ode_py + script, locals);
53+ #endif
54+
55+ #ifdef NRN_ENABLE_COVERAGE
56+ const auto & path = fs::current_path () / fmt::format (" coverage_{}.xml" , suffix);
57+ py::exec (fmt::format (R"(
58+ cov.stop()
59+ cov.save()
60+ # Check if we have any coverage data
61+ data = cov.get_data()
62+ if data.measured_files():
63+ cov.xml_report(outfile='{}')
64+ )" ,
65+ path.string ()),
66+ locals);
67+ #endif
68+ }
69+
2870std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
2971call_solve_linear_system (const std::vector<std::string>& eq_system,
3072 const std::vector<std::string>& state_vars,
@@ -57,8 +99,7 @@ except Exception as e:
5799 new_local_vars = [""]
58100 exception_message = traceback.format_exc()
59101)" ;
60-
61- py::exec (nmodl::pybind_wrappers::ode_py + script, locals);
102+ run_python_script (script, locals);
62103 // returns a vector of solutions, i.e. new statements to add to block:
63104 auto solutions = locals[" solutions" ].cast <std::vector<std::string>>();
64105 // and a vector of new local variables that need to be declared in the block:
@@ -93,7 +134,7 @@ except Exception as e:
93134 exception_message = traceback.format_exc()
94135)" ;
95136
96- py::exec (nmodl::pybind_wrappers::ode_py + script, locals);
137+ run_python_script ( script, locals);
97138 // returns a vector of solutions, i.e. new statements to add to block:
98139 auto solutions = locals[" solutions" ].cast <std::vector<std::string>>();
99140 // may also return a python exception message:
@@ -130,7 +171,7 @@ except Exception as e:
130171 exception_message = traceback.format_exc()
131172)" ;
132173
133- py::exec (nmodl::pybind_wrappers::ode_py + script, locals);
174+ run_python_script ( script, locals);
134175 } else if (method == codegen::naming::CNEXP_METHOD ) {
135176 // replace x' = f(x) differential equation
136177 // with analytic solution for x(t+dt) in terms of x(t)
@@ -147,7 +188,7 @@ except Exception as e:
147188 exception_message = traceback.format_exc()
148189)" ;
149190
150- py::exec (nmodl::pybind_wrappers::ode_py + script, locals);
191+ run_python_script ( script, locals);
151192 } else {
152193 // nothing to do, but the caller should know.
153194 return {};
@@ -179,7 +220,7 @@ except Exception as e:
179220 exception_message = traceback.format_exc()
180221)" ;
181222
182- py::exec (nmodl::pybind_wrappers::ode_py + script, locals);
223+ run_python_script ( script, locals);
183224
184225 auto solution = locals[" solution" ].cast <std::string>();
185226 auto exception_message = locals[" exception_message" ].cast <std::string>();
@@ -223,7 +264,7 @@ except Exception as e:
223264 statements,
224265 property.has_value () ? fmt::format (" {}[{}]" , name, property.value ()) : name);
225266
226- py::exec (nmodl::pybind_wrappers::ode_py + script, locals);
267+ run_python_script ( script, locals);
227268
228269 auto solution = locals[" solution" ].cast <std::string>();
229270 auto exception_message = locals[" exception_message" ].cast <std::string>();
0 commit comments