Skip to content

Commit 4e48af8

Browse files
authored
Merge branch 'master' into add-nrn_stack_pops
2 parents 38f0f63 + e99f020 commit 4e48af8

30 files changed

Lines changed: 1803 additions & 23 deletions

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ set(NRN_RX3D_OPT_LEVEL
216216
"${NRN_RX3D_OPT_LEVEL_DEFAULT}"
217217
CACHE STRING "Optimization level for Cython generated files (non-zero may compile slowly)")
218218

219-
option(NRN_ENABLE_COVERAGE "EnableCode Coverage (make cover_begin, make cover_html)" OFF)
219+
option(NRN_ENABLE_COVERAGE "EnableCode Coverage (cover-begin, cover-html, cover-diff)" OFF)
220220
set(NRN_COVERAGE_FILES
221221
""
222222
CACHE STRING "semicolon (;) separated list of files to collect code coverage")

bin/CMakeLists.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ include(CMakeListsNrnMech)
3838
# =============================================================================
3939
configure_file("nrngui.in" "nrngui" @ONLY)
4040
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sortspike ${CMAKE_CURRENT_BINARY_DIR}/sortspike COPYONLY)
41+
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/rdcellstate ${CMAKE_CURRENT_BINARY_DIR}/rdcellstate
42+
COPYONLY)
4143
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nrnivmodl_makefile_cmake.in
4244
${PROJECT_BINARY_DIR}/bin/nrnmech_makefile @ONLY)
4345
string(JOIN " " NRN_PYTHON_VERSIONS_STRING ${NRN_PYTHON_VERSIONS})
@@ -98,6 +100,7 @@ if(NRN_ENABLE_CORENEURON)
98100
endif()
99101

100102
install(FILES ${PROJECT_BINARY_DIR}/bin/nrnmech_makefile DESTINATION ${NRN_INSTALL_DATA_PREFIX}bin)
101-
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/sortspike ${CMAKE_CURRENT_SOURCE_DIR}/mkthreadsafe
102-
${PROJECT_BINARY_DIR}/bin/nrnpyenv.sh ${CMAKE_CURRENT_SOURCE_DIR}/set_nrnpyenv.sh
103-
DESTINATION ${NRN_INSTALL_DATA_PREFIX}bin)
103+
install(
104+
PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/sortspike ${CMAKE_CURRENT_BINARY_DIR}/rdcellstate
105+
${CMAKE_CURRENT_SOURCE_DIR}/mkthreadsafe ${PROJECT_BINARY_DIR}/bin/nrnpyenv.sh
106+
${CMAKE_CURRENT_SOURCE_DIR}/set_nrnpyenv.sh DESTINATION ${NRN_INSTALL_DATA_PREFIX}bin)

bin/rdcellstate

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env python3
2+
"""Thin CLI wrapper for neuron.debug.rdcellstate."""
3+
from neuron.debug.rdcellstate import main
4+
5+
raise SystemExit(main())

ci/coverage_files_from_diff.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
# Emit a semicolon-separated source file list for -DNRN_COVERAGE_FILES=...
3+
# Usage: coverage_files_from_diff.sh [base-ref]
4+
# Default base-ref is master (three-dot diff: base...HEAD).
5+
# Exits 1 with a stderr message when no compiled sources (.cpp/.c) changed.
6+
set -euo pipefail
7+
8+
base="${1:-master}"
9+
mapfile -t files < <(git diff --name-only "${base}...HEAD" | grep -E '\.(cpp|c)$' || true)
10+
if ((${#files[@]} == 0)); then
11+
echo "No compiled sources (.cpp/.c) changed vs ${base}; omit -DNRN_COVERAGE_FILES" >&2
12+
exit 1
13+
fi
14+
(IFS=';'; echo "${files[*]}")

cmake/Coverage.cmake

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
# NRN_COVERAGE_FILES speeds the workflow tremendously, when iteratively
1313
# working on a single or a few files.
1414
#
15-
# Two targets are created: cover_begin and cover_html.
15+
# Targets: cover_begin (alias cover-begin), cover_html (cover-html),
16+
# and cover_diff (cover-diff, requires diff-cover).
1617
#
1718
# cover_begin erases all the *.gcda coverage files and
1819
# creates a baseline report (coverage-base.info)
@@ -124,6 +125,19 @@ if(NRN_ENABLE_COVERAGE)
124125
set(cover_combine_command "${LCOV}" "--add-tracefile" "coverage-base.info" "--add-tracefile"
125126
"coverage-run.info" "--output-file" "coverage-combined.info")
126127
set(cover_html_command genhtml "coverage-combined.info" "--output-directory" html)
128+
129+
set(NRN_COVERAGE_DIFF_BRANCH
130+
"master"
131+
CACHE STRING "Git branch for cover_diff (diff-cover --compare-branch)")
132+
133+
find_program(DIFF_COVER diff-cover)
134+
if(DIFF_COVER)
135+
# diff-cover matches git diff paths to lcov SF: paths via GitPathTool, which is relative to cwd.
136+
# Run from PROJECT_SOURCE_DIR (not the build dir) so repo-root paths like src/foo.cpp align with
137+
# the lcov report.
138+
set(cover_diff_report "${PROJECT_BINARY_DIR}/html-diff/index.html")
139+
endif()
140+
127141
add_custom_target(
128142
cover_clean
129143
COMMAND ${cover_clean_command}
@@ -137,6 +151,7 @@ if(NRN_ENABLE_COVERAGE)
137151
COMMAND ${cover_clean_command}
138152
COMMAND ${cover_baseline_command}
139153
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}")
154+
add_custom_target(cover-begin DEPENDS cover_begin)
140155
add_custom_target(
141156
cover_collect
142157
COMMAND ${cover_collect_command}
@@ -152,4 +167,22 @@ if(NRN_ENABLE_COVERAGE)
152167
COMMAND ${cover_html_command}
153168
COMMAND echo "View in browser at file://${PROJECT_BINARY_DIR}/html/index.html"
154169
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}")
170+
add_custom_target(cover-html DEPENDS cover_html)
171+
172+
if(DIFF_COVER)
173+
add_custom_target(
174+
cover_diff
175+
COMMAND ${cover_collect_command}
176+
COMMAND ${cover_combine_command}
177+
COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/html-diff"
178+
COMMAND
179+
${CMAKE_COMMAND} -E chdir "${PROJECT_SOURCE_DIR}" "${DIFF_COVER}"
180+
"${PROJECT_BINARY_DIR}/coverage-combined.info" "--compare-branch"
181+
"${NRN_COVERAGE_DIFF_BRANCH}" "--show-uncovered" "--format" "html:${cover_diff_report}"
182+
COMMAND echo "View changed-line coverage at file://${cover_diff_report}"
183+
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}")
184+
add_custom_target(cover-diff DEPENDS cover_diff)
185+
else()
186+
message(STATUS "diff-cover not found; cover_diff target unavailable (pip install diff-cover)")
187+
endif()
155188
endif()

docs/capi.rst

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,61 @@ Sections
364364
The ``nrn_Item*`` returned can be used for loops in the same way as the ``all_sections`` variable in
365365
the example in :c:func:`nrn_allsec`.
366366
367+
.. c:function:: Section* nrn_section_parent(Section* sec)
368+
369+
Return the Section that ``sec`` is connected to, or ``NULL`` if ``sec`` is a
370+
root.
371+
372+
This is the direct connectivity, the same Section a HOC ``SectionRef``'s
373+
``parent`` yields. It reads the connectivity directly, without creating a
374+
``SectionRef`` object.
375+
376+
:param sec: The Section whose parent is wanted.
377+
:returns: The parent Section, or ``NULL`` if ``sec`` has no parent (or is
378+
``NULL``).
379+
380+
.. c:function:: Section* nrn_section_trueparent(Section* sec)
381+
382+
Return the *true* parent of ``sec``, or ``NULL`` if there is none.
383+
384+
The true parent (``SectionRef``'s ``trueparent``) is normally the parent,
385+
but a Section connected to the ``0`` end of its parent shares that parent's
386+
true parent, so the relationship climbs until a connection that is not at
387+
the parent's beginning.
388+
389+
:param sec: The Section whose true parent is wanted.
390+
:returns: The true parent Section, or ``NULL`` if there is none (or ``sec``
391+
is ``NULL``).
392+
393+
.. c:function:: Section* nrn_section_child(Section* sec)
394+
395+
Return the first child Section connected to ``sec``, or ``NULL`` if it has
396+
none.
397+
398+
Walk the remaining children with :c:func:`nrn_section_sibling`. The order
399+
matches ``SectionRef``'s ``child[i]``.
400+
401+
:param sec: The parent Section.
402+
:returns: The first child Section, or ``NULL`` (also if ``sec`` is ``NULL``).
403+
404+
.. c:function:: Section* nrn_section_sibling(Section* sec)
405+
406+
Return the next Section that shares ``sec``'s parent, or ``NULL`` if ``sec``
407+
is the last child.
408+
409+
Paired with :c:func:`nrn_section_child`, this iterates every child of a
410+
Section:
411+
412+
.. code-block:: c
413+
414+
for (Section* c = nrn_section_child(parent); c; c = nrn_section_sibling(c)) {
415+
printf("%s\n", nrn_secname(c));
416+
}
417+
418+
:param sec: A Section.
419+
:returns: The next sibling Section, or ``NULL`` (also if ``sec`` is
420+
``NULL``).
421+
367422
.. c:function:: bool nrn_section_is_active(const Section* sec)
368423
369424
Check if a Section is active (exists and is valid).
@@ -668,6 +723,112 @@ Segments
668723
seg.g_pas = 0.001 # S/cm²
669724
670725
726+
.. c:function:: int nrn_setpointer_pop(Symbol* pointer_sym, Section* sec, double x, char* error_msg, size_t error_msg_size)
727+
728+
Wire an NMODL ``POINTER`` variable to the source pointer on top of the stack.
729+
730+
The source is whatever pointer the caller has pushed, e.g. with
731+
:c:func:`nrn_rangevar_push`. Pushing the source rather than naming it lets
732+
this single function accept a pointer obtained any way the stack supports,
733+
instead of enumerating source kinds. The pushed pointer is consumed (popped)
734+
even on the error paths, so the stack is left balanced.
735+
736+
:param pointer_sym: Symbol of the ``POINTER`` range variable to wire (the
737+
target).
738+
:param sec: Section of the mechanism instance owning the POINTER.
739+
:param x: Normalized position (0.0 to 1.0) of that instance.
740+
:param error_msg: Buffer filled with a message on failure (may be ``NULL``).
741+
:param error_msg_size: Size of ``error_msg``.
742+
:returns: 0 on success; nonzero on error, with ``error_msg`` populated when
743+
``pointer_sym`` is not a ``POINTER`` variable or its mechanism is not
744+
present at the target segment.
745+
746+
This addresses the target by ``(sec, x)``, which identifies a density
747+
mechanism's single instance at a segment. For a **point process**, where
748+
several instances may share one location, use
749+
:c:func:`nrn_pp_setpointer_pop`, which addresses the target by instance
750+
object instead.
751+
752+
This is the C-API equivalent of the HOC ``setpointer`` statement and of
753+
assigning a ``_ref_`` to a POINTER in Python. It stores a data handle to the
754+
source, so the connection survives internal data reordering.
755+
756+
**C Usage:**
757+
758+
.. code-block:: c
759+
760+
// A density mechanism `cufl` has a POINTER `pv`. Wire dend's instance to
761+
// read soma(0.5).v instead of its own segment's voltage. A density
762+
// mechanism has one instance per segment, so (dend, 0.5) names it.
763+
Symbol* pv = nrn_symbol("pv_cufl");
764+
Symbol* v = nrn_symbol("v");
765+
char err[256];
766+
nrn_rangevar_push(v, soma, 0.5); // push the source pointer
767+
if (nrn_setpointer_pop(pv, dend, 0.5, err, sizeof(err))) {
768+
fprintf(stderr, "setpointer failed: %s\n", err);
769+
}
770+
771+
**Python Equivalent:**
772+
773+
.. code-block:: python
774+
775+
# cufl is a density mechanism (SUFFIX) with a POINTER pv
776+
dend(0.5).cufl._ref_pv = soma(0.5)._ref_v
777+
778+
.. seealso::
779+
780+
:c:func:`nrn_pp_setpointer_pop`, :c:func:`nrn_rangevar_push`
781+
782+
.. c:function:: int nrn_pp_setpointer_pop(Object* pp, const char* name, char* error_msg, size_t error_msg_size)
783+
784+
Wire a point process's NMODL ``POINTER`` variable to the source pointer on
785+
top of the stack.
786+
787+
This is the point-process counterpart to :c:func:`nrn_setpointer_pop`. A
788+
point process is addressed by its instance object rather than by ``(sec,
789+
x)``: several point processes may occupy one location (two half-gaps at one
790+
segment, say), so the segment alone cannot identify which instance owns the
791+
``POINTER`` slot. The ``POINTER`` is named within the point process's own
792+
symbol table, exactly as in :c:func:`nrn_property_get`.
793+
794+
As with :c:func:`nrn_setpointer_pop`, the source is whatever pointer the
795+
caller has pushed (e.g. with :c:func:`nrn_rangevar_push` or
796+
:c:func:`nrn_property_push`), and it is consumed even on the error paths, so
797+
the stack is left balanced.
798+
799+
:param pp: The point process instance whose ``POINTER`` is the target.
800+
:param name: Name of the ``POINTER`` variable within the point process.
801+
:param error_msg: Buffer filled with a message on failure (may be ``NULL``).
802+
:param error_msg_size: Size of ``error_msg``.
803+
:returns: 0 on success; nonzero on error, with ``error_msg`` populated when
804+
``pp`` is not a point process, ``name`` is not one of its ``POINTER``
805+
variables, or the point process is not located in a section.
806+
807+
**C Usage:**
808+
809+
.. code-block:: c
810+
811+
// Wire a half-gap point process's vgap POINTER to the peer cell's
812+
// voltage: cell2's membrane potential drives the gap current the
813+
// HalfGap instance on cell1 computes. A true half gap wires both ways.
814+
char err[256];
815+
nrn_rangevar_push(nrn_symbol("v"), cell2, 0.5); // push the source pointer
816+
if (nrn_pp_setpointer_pop(halfgap1, "vgap", err, sizeof(err))) {
817+
fprintf(stderr, "setpointer failed: %s\n", err);
818+
}
819+
820+
**Python Equivalent:**
821+
822+
.. code-block:: python
823+
824+
# halfgap1 is a POINT_PROCESS instance with a POINTER vgap
825+
halfgap1._ref_vgap = cell2(0.5)._ref_v
826+
827+
.. seealso::
828+
829+
:c:func:`nrn_setpointer_pop`, :c:func:`nrn_property_push`, :c:func:`nrn_rangevar_push`
830+
831+
671832
Functions, objects, and the stack
672833
---------------------------------
673834

docs/cmake_doc/options.rst

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -572,16 +572,20 @@ NRN_ENABLE_COVERAGE:BOOL=OFF
572572

573573
Requires ``lcov`` (e.g. ``sudo apt install lcov``).
574574

575-
Provides two make targets to simplify the repeated "run tests, examine coverage"
575+
Provides make targets to simplify the repeated "run tests, examine coverage"
576576
workflow.
577-
-- ``make cover_begin`` erases all previous coverage data
577+
-- ``make cover-begin`` (``cover_begin``) erases all previous coverage data
578578
(``*.gcda`` files), and creates a baseline report. (Note all files and
579579
folders are created in the ``CMAKE_BINARY_DIR`` where you ran cmake.)
580580

581-
-- ``make cover_html`` creates a coverage report for the sum of all the
582-
software runs since the last ``cover_begin`` and prints a file url
581+
-- ``make cover-html`` (``cover_html``) creates a coverage report for the sum of all the
582+
software runs since the last ``cover-begin`` and prints a file url
583583
that you can paste into your browser to review the coverage.
584584

585+
-- ``make cover-diff`` (``cover_diff``; requires ``pip install diff-cover``) reports
586+
coverage on changed lines vs. ``NRN_COVERAGE_DIFF_BRANCH`` (default
587+
``master``). See `Developer Builds: Code Coverage <../install/code_coverage.html>`_.
588+
585589
When using an iterative workflow to examine test coverage of a single
586590
or a few files, the above targets run much faster when this option is
587591
combined with `NRN_COVERAGE_FILES:STRING=`_
@@ -596,14 +600,19 @@ NRN_COVERAGE_FILES:STRING=
596600

597601
``-DNRN_COVERAGE_FILES="src/nrniv/partrans.cpp;src/nmodl/parsact.cpp;src/nrnpython/nrnpy_hoc.cpp"``
598602

599-
For a list of all the cpp files changed in a pull request, consider
600-
copy/pasting the ``;`` separated list obtained with
603+
For a list of compiled sources changed on a branch, use
601604

602605
.. code-block:: shell
603606
604-
a=`git diff --name-only master | grep '\.cpp'`
605-
echo $a | sed 's/ /;/g'
607+
ci/coverage_files_from_diff.sh master
608+
609+
The script lists ``.cpp`` and ``.c`` files only. It exits with an error
610+
when none changed (omit this option in that case).
606611

612+
NRN_COVERAGE_DIFF_BRANCH:STRING=master
613+
--------------------------------------
614+
Git branch passed to ``diff-cover --compare-branch`` for the
615+
``cover_diff`` target. Defaults to ``master``.
607616

608617
NRN_SANITIZERS:STRING=
609618
----------------------

docs/install/code_coverage.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
## Dependencies (Linux)
55
```
66
sudo apt install lcov
7+
pip install diff-cover
78
```
89

910
## Instructions
@@ -88,9 +89,43 @@ cmake .. -DCMAKE_INSTALL_PREFIX=install \
8889
8990
make -j 6 install
9091
91-
make cover_begin
92+
make cover-begin
9293
9394
make test
9495
95-
make cover_html
96+
make cover-html
9697
```
98+
99+
## Changed-line coverage (pull requests)
100+
101+
Whole-file ``cover_html`` reports do not distinguish lines you changed from
102+
legacy uncovered code. For pull-request iteration, use ``cover_diff`` after
103+
running tests. It wraps `diff-cover <https://github.com/Bachmann1234/diff_cover>`_
104+
and reports coverage on the diff against a base branch (Codecov "patch coverage"
105+
uses the same idea).
106+
107+
```
108+
ninja cover-begin
109+
ctest -j8 -R 'some_test_pattern'
110+
ninja cover-diff
111+
```
112+
113+
``cover_diff`` collects coverage, then prints uncovered **changed** lines on the
114+
console and writes ``html-diff/index.html``.
115+
116+
The comparison branch defaults to ``master``. Override at configure time:
117+
118+
```
119+
cmake .. -DNRN_COVERAGE_DIFF_BRANCH=master ...
120+
```
121+
122+
Optional: limit instrumentation to compiled sources changed on your branch:
123+
124+
```
125+
files=$(../ci/coverage_files_from_diff.sh master)
126+
cmake .. -DNRN_ENABLE_COVERAGE=ON -DNRN_COVERAGE_FILES="${files}" ...
127+
```
128+
129+
The script lists only ``.cpp`` and ``.c`` files (headers are not compiled).
130+
It exits with an error when no such files changed; in that case omit
131+
``-DNRN_COVERAGE_FILES`` and use full or ``cover_diff`` reporting only.

0 commit comments

Comments
 (0)