Skip to content

ci(cmake_tests): run the main test gate under ASan (debug-openblas-cpu)#1060

Open
IvanaGyro wants to merge 2 commits into
masterfrom
claude/issue-1052-ci-asan-preset
Open

ci(cmake_tests): run the main test gate under ASan (debug-openblas-cpu)#1060
IvanaGyro wants to merge 2 commits into
masterfrom
claude/issue-1052-ci-asan-preset

Conversation

@IvanaGyro

@IvanaGyro IvanaGyro commented Jul 15, 2026

Copy link
Copy Markdown
Member

Problem

The ci-cmake_tests job builds the "Editable install" step with CMAKE_BUILD_TYPE=Debug + RUN_TESTS=ON, but explicitly leaves USE_DEBUG=OFF (the preset default), so -fsanitize=address is never applied. AddressSanitizer therefore never runs in the main CI gate. This let a real overlapping-memcpy UB bug (BlockUniTensor::combineBonds, #1052) sit undetected for a long time — it only surfaces locally when a developer happens to build with a debug-* preset.

Fix

  • Switch the "Editable install" step to configure via --config-settings=cmake.args=--preset=debug-openblas-cpu instead of individual cmake.define overrides. The preset already sets CMAKE_BUILD_TYPE=Debug, RUN_TESTS=ON, and USE_DEBUG=ON, so this job now runs the whole suite (ctest + pytest) under ASan. build-dir=build is kept so ctest/gcovr still find a stable path.
  • Rewrote the step's comment block, which previously justified why USE_DEBUG stayed off; that rationale is now inverted.
  • Added an LD_PRELOAD/ASAN_OPTIONS shim to the two Python-hosted steps only ("Sanity-check the install" and "Run pytest with Python coverage"): ASan-instrumented cytnx.so loaded into the uninstrumented python binary needs libasan.so preloaded, or every native (C++-side) exception crashes the process — ASan's __cxa_throw interceptor CHECK-fails because plain python doesn't link libstdc++, so it can't resolve the real symbol at init. libstdc++.so must be preloaded alongside libasan.so for the same reason. ASAN_OPTIONS=detect_leaks=0 is also needed for these two steps — LeakSanitizer's exit-time scan otherwise reports CPython's own deliberately-not-cleaned-up interpreter state (interned strings, static type objects, allocator arenas) as leaks, unrelated to whether Cytnx itself leaks.
  • Left "Build the gtest binary" / "Run gtest via ctest" untouched — test_main links against ASan natively and needs no shim, and this keeps leak detection on for the one place a genuine Cytnx C++ leak would actually be caught.
  • Updated CLAUDE.md's CI-gates section to note this gate now runs under debug-openblas-cpu with ASan, in a separate commit from the workflow change. Checked GEMINI.md for overlapping review guidance — it does not mention ci-cmake_tests or ASan status anywhere, so it was left unchanged.

This PR does not fix #1052 itself (that's a separate PR). ctest is expected to show exactly one known failure, BlockUniTensorTest.CombineBondForceDoesNotRewriteUserHeldBond, until the separate #1052 fix PR merges. Related to #1052 (not closing it).

Testing

  • Local debug-openblas-cpu build + ctest (via this repo's build-test-workflow skill): 1784 tests run, 1783 passed, 1 failed — the failure is exactly BlockUniTensorTest.CombineBondForceDoesNotRewriteUserHeldBond, matching the known pre-existing ASan memcpy-param-overlap in BlockUniTensor::permute_ (via combineBond), CombineBondForceDoesNotRewriteUserHeldBond #1052 bug, and nothing else.
  • Local pytest run under the ASan LD_PRELOAD/ASAN_OPTIONS shim is still in progress (the Python-bindings build for this preset takes a while on this machine) — it will be confirmed as a follow-up comment/push here.
  • Opening this as a draft now per plan: since this PR itself modifies ci-cmake_tests.yml, the same-repo CI run on this PR branch exercises the actual modified workflow (preset switch + LD_PRELOAD shim) and is the most direct verification of whether the ASan setup works end-to-end. Watching CI once it starts and will fix anything it catches.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates CLAUDE.md to document that the ci-cmake_tests CI gate now builds under the debug-openblas-cpu preset with USE_DEBUG=ON and AddressSanitizer enabled. It advises developers to reproduce issues locally using this preset rather than a release-mode CPU build to catch memory-safety issues. There are no review comments, so I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread CLAUDE.md Outdated
Comment thread .github/workflows/ci-cmake_tests.yml Outdated
IvanaGyro added a commit that referenced this pull request Jul 15, 2026
…te once

Two review findings from IvanaGyro on PR #1060:

1. Hardcoding `gcc` to locate libasan.so/libstdc++.so has the same
   compiler-mismatch gap gemini-code-assist flagged in PR #1061's copy
   of this exact pattern: it preloads whatever `gcc` resolves to on
   PATH regardless of which compiler actually built cytnx.so, and
   -print-file-name silently returns its bare input filename (a
   relative path) when it can't resolve a library, yielding an
   unusable relative LD_PRELOAD path. Read CMAKE_CXX_COMPILER from the
   configured build's own CMakeCache.txt instead, confirm it
   identifies as GCC (Clang's --version has no "Free Software
   Foundation" line), and require both resolved library paths to be
   absolute. Unlike the skill script's graceful degradation (silently
   skip the shim), this CI job's toolchain is always GCC by
   construction (the `compilers` conda package installed earlier in
   this job) -- an unexpected result here means the environment
   changed unexpectedly, so fail loudly with `::error::` rather than
   silently skip the shim and hit a confusing crash two steps later.

2. Compute the resolved LD_PRELOAD value once and reuse it, instead of
   recomputing `gcc -print-file-name` twice (once per Python-hosted
   step). Added a dedicated "Resolve ASan runtime paths" step that
   writes the resolved value to $GITHUB_OUTPUT, referenced by both
   "Sanity-check the install" and "Run pytest with Python coverage"
   via their own step-scoped `env:`. Deliberately NOT hoisted to
   $GITHUB_ENV (job-wide): that would leak ASAN_OPTIONS=detect_leaks=0
   into "Run gtest via ctest" too, undoing the existing design that
   keeps leak detection on for the one place a genuine Cytnx C++ leak
   would actually be caught.

Verified locally: build/debug-openblas-cpu/CMakeCache.txt's
CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/c++ resolves via the new lookup
to GCC 13.3.0 and absolute paths for both libraries; re-ran the
resulting LD_PRELOAD/ASAN_OPTIONS values against `python -c "import
cytnx"` end-to-end -- succeeds, matching the prior gcc-hardcoded
behavior.

Co-Authored-By: Claude <noreply@anthropic.com>
IvanaGyro added a commit that referenced this pull request Jul 15, 2026
IvanaGyro's review on PR #1060: the added ci-cmake_tests bullet detail
is redundant with what the build-test-workflow skill's own docs
already cover for reproducing this gate locally under debug-openblas-cpu.
Reverts to the original one-line bullet.

Co-Authored-By: Claude <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.94%. Comparing base (e37e3c8) to head (7fe6b02).
⚠️ Report is 51 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff             @@
##           master    #1060       +/-   ##
===========================================
+ Coverage   60.49%   72.94%   +12.44%     
===========================================
  Files         229      226        -3     
  Lines       31873    28225     -3648     
  Branches       71       71               
===========================================
+ Hits        19282    20588     +1306     
+ Misses      12570     7616     -4954     
  Partials       21       21               
Flag Coverage Δ
cpp 73.06% <ø> (+12.61%) ⬆️
python 64.13% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
C++ backend 71.96% <ø> (+13.91%) ⬆️
Python bindings 79.05% <ø> (+4.04%) ⬆️
Python package 64.13% <ø> (ø)
see 60 files with indirect coverage changes

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e37e3c8...7fe6b02. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@IvanaGyro

Copy link
Copy Markdown
Member Author

The failed test is fixed by #1054.

@IvanaGyro
IvanaGyro marked this pull request as ready for review July 15, 2026 13:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 940f286400

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci-cmake_tests.yml
The "Editable install" step built with CMAKE_BUILD_TYPE=Debug and
RUN_TESTS=ON via individual cmake.define overrides, but left USE_DEBUG at
the preset default OFF, so -fsanitize=address was never applied to the
cytnx library. That let a real overlapping-memcpy UB bug
(BlockUniTensor::combineBonds, #1052) go undetected in CI for a long
time -- it only surfaced when a developer happened to build locally with a
debug-* preset.

Switch the step to configure via --preset=debug-openblas-cpu instead of
individual cmake.define flags; the preset already sets
CMAKE_BUILD_TYPE=Debug, RUN_TESTS=ON, and USE_DEBUG=ON, so this is now the
main CI gate running under ASan.

ASan-instrumented cytnx.so loaded into the uninstrumented `python` binary
needs libasan.so and libstdc++.so preloaded via LD_PRELOAD, or every
native (C++-side) exception crashes the process: ASan's __cxa_throw
interceptor CHECK-fails because plain `python` doesn't link libstdc++, so
it can't resolve the real symbol at init. Add that preload, plus
ASAN_OPTIONS=detect_leaks=0 (LeakSanitizer's exit-time scan otherwise
reports CPython's own deliberately-not-cleaned-up interpreter state as
leaks), to the two Python-hosted steps: "Sanity-check the install" and
"Run pytest with Python coverage". The gtest steps are left untouched --
test_main links against ASan natively and needs no shim, and this keeps
leak detection on for the one place a genuine Cytnx C++ leak would
actually be caught.

The compiler used for the preload is read from the configured build's own
CMakeCache.txt CMAKE_CXX_COMPILER entry, not a bare `gcc` guess on PATH:
preloading one GCC's libasan.so into a binary built by a different
compiler/GCC version is an ASan runtime/ABI mismatch, and
-print-file-name silently returns its bare input filename (a relative
path) when it can't resolve the library, yielding an unusable relative
LD_PRELOAD path. This job's toolchain is always GCC (the `compilers`
conda package installed earlier in the job), so failing to resolve a GCC
compiler here means the environment changed unexpectedly -- fail loudly
with `::error::` rather than silently continue and hit a confusing crash
two steps later.

The lookup runs once in a dedicated "Resolve ASan runtime paths" step
that writes the resolved LD_PRELOAD value to $GITHUB_OUTPUT, reused by
both Python-hosted steps via their own step-scoped `env:` instead of
recomputing `gcc -print-file-name` twice. Deliberately not hoisted to
$GITHUB_ENV (job-wide): that would leak ASAN_OPTIONS=detect_leaks=0 into
"Run gtest via ctest" too, undoing the deliberate leak-detection-on
behavior for that step.

Verified locally: build/debug-openblas-cpu/CMakeCache.txt's
CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/c++ resolves via this lookup to GCC
13.3.0 and absolute paths for both libraries; ran the resulting
LD_PRELOAD/ASAN_OPTIONS values against `python -c "import cytnx"`
end-to-end -- succeeds.

Co-Authored-By: Claude <noreply@anthropic.com>
@IvanaGyro
IvanaGyro force-pushed the claude/issue-1052-ci-asan-preset branch from 940f286 to a965368 Compare July 15, 2026 19:02
@IvanaGyro
IvanaGyro requested review from pcchen and yingjerkao July 16, 2026 08:41
… the ASan run

The ASan gate runs the full ctest suite serially. HPTT's OpenMP worker
threads and OpenBLAS's thread pool busy-spin between the tiny,
microsecond-spaced parallel regions the iterative eigensolver suites
(Lanczos/Arnoldi, issue #1058) trigger; on the runner's few cores those
spinners oversubscribe the CPU and inflate ctest wall time several-fold.

Pin libopenblas=*=*openmp* so OpenBLAS shares the single llvm OpenMP
runtime HPTT and cytnx's -fopenmp code already use -- conda-forge's openmp
OpenBLAS hard-requires _openmp_mutex=*_llvm, which also makes the previously
explicit mutex pin redundant. Set OMP_WAIT_POLICY=passive so idle worker
threads sleep instead of spinning. One knob then governs all OpenMP
threading in the job.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ASan memcpy-param-overlap in BlockUniTensor::permute_ (via combineBond), CombineBondForceDoesNotRewriteUserHeldBond

1 participant