Files
ConformalLabpp/code/tests/cgal/CMakeLists.txt
Tarik Moussa 449c5899c0 ci: re-enable CGAL tests with LOW_MEMORY_BUILD for Raspberry Pi runner
Finding-I from doc/reviewer/external-audit-2026-05-30.md.

Root cause: CGAL+Eigen at -O3 drives cc1plus peak RAM to ~700 MB per
Unity compilation unit (batch size 4) on ARM64.  With the 1600 MB
container limit the 2nd or 3rd TU reliably triggered OOM-kill, so
test-cgal was gated off via `if: false` since 2026-05-26.

Fix: new CMake option CONFORMALLAB_LOW_MEMORY_BUILD=ON applies four
orthogonal memory-saving measures to conformallab_cgal_tests:

  1. -O0 (no debug info): optimizer passes entirely skipped → cc1plus
     peak drops from ~700 MB to ~150-200 MB per TU on ARM64.
     Omitting -g avoids the additional object-file / linker RAM cost.

  2. CONFORMALLAB_USE_PCH=OFF: saves the one-time ~200 MB PCH
     compilation cost; each TU re-parses CGAL headers (fast at -O0).

  3. UNITY_BUILD_BATCH_SIZE=1: one source file per cc1plus invocation,
     removing the "4-file template-explosion" per-unit multiplier.

  4. -Wl,--no-keep-memory (GNU ld): linker releases symbol tables after
     each input file → ~15-25 % less linker RSS.

Verified locally with cmake -DCONFORMALLAB_LOW_MEMORY_BUILD=ON:
  277/277 CGAL tests pass, 31 s runtime (vs 2 s at -O3 — expected;
  tests run 15× slower without optimizer but all correct).

CI workflow changes (cpp-tests.yml):
  - test-cgal re-enabled: `if: github.event_name == 'pull_request'`
  - Configure step adds -DCONFORMALLAB_LOW_MEMORY_BUILD=ON
  - Container memory: 1600m → 2000m (--memory-swap=3000m for 1 GB swap
    headroom), using ~half of the Pi's 3-4 GB while leaving OS margin.

CLAUDE.md updated: new flag added to compile-time options table; CI
status row corrected from DISABLED to active.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 01:17:27 +02:00

262 lines
12 KiB
CMake
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# tests/cgal/CMakeLists.txt
#
# CGAL-dependent test target. Only built when -DWITH_CGAL=ON.
# Requires Boost (find_package(Boost REQUIRED) is called in the root CMakeLists).
#
# Run with:
# cmake -S code -B build -DWITH_CGAL=ON
# cmake --build build --target conformallab_cgal_tests
# ctest --test-dir build -R cgal
add_executable(conformallab_cgal_tests
# ── Phase 3a: mesh infrastructure ──────────────────────────────────────
test_conformal_mesh.cpp
# ── Phase 3b: HyperIdealFunctional ─────────────────────────────────────
test_hyper_ideal_functional.cpp
# ── Phase 3c + 3e: SphericalFunctional + gauge-fix ────────────────────
test_spherical_functional.cpp
# ── Phase 3d: EuclideanCyclicFunctional ───────────────────────────────
test_euclidean_functional.cpp
# ── Phase 3f: Hessians (cotangent Laplacian, spherical + Euclidean) ───
test_euclidean_hessian.cpp
test_spherical_hessian.cpp
# ── Phase 9b: Hyper-ideal Hessian — block-FD vs full-FD validation ───
# Verifies the O(F·36) block-local Hessian agrees with the
# O(F·n) full-FD baseline. Java upstream has no Hessian at all
# (HyperIdealFunctional.hasHessian() returns false) — both
# variants are conformallab++ extensions beyond the port.
test_hyper_ideal_hessian.cpp
# ── Phase 4a: Newton solver ────────────────────────────────────────────
test_newton_solver.cpp
# ── Phase 4b: Mesh I/O (CGAL::IO) ─────────────────────────────────────
test_mesh_io.cpp
# ── Phase 4c: End-to-end pipeline + user examples ─────────────────────
test_pipeline.cpp
# ── Phase 5: Layout / embedding + serialization ────────────────────────
test_layout.cpp
# ── Phase 6: GaussBonnet, cut graph, exact trilateration, normalisation
test_phase6.cpp
# ── Phase 7: Java-parity layout — MobiusMap, priority BFS, halfedge_uv,
# period matrix, fundamental domain, tiling
test_phase7.cpp
# ── Java parity: geometry utility tests ──────────────────────────────────
# Ported from CuttinUtilityTest, UnwrapUtilityTest,
# ConvergenceUtilityTests, HomologyTest. All tests active —
# the v0.7.0 genus-2 homology stub was implemented in Phase 7
# (HomologyGenerators.Genus2_FourCutEdges, brezel2.obj).
test_geometry_utils.cpp
# ── Scalability smoke tests ────────────────────────────────────────────────
# Newton convergence on large real-world meshes (cathead, brezel, brezel2).
# Assert correctness only (< 30 iterations, ||G|| < 1e-8).
# Wall-clock time is printed for documentation but NOT asserted,
# so the tests remain stable on slow CI hardware (Raspberry Pi ARM64).
test_scalability_smoke.cpp
# ── Phase 8 MVP: new CGAL-style public API ────────────────────────────────
# First client of Conformal_map_traits.h + Discrete_conformal_map.h.
# Acceptance probe before Phase 9a (Inversive-Distance) lands.
test_cgal_traits_mvp.cpp
# ── Phase 9a.1: CPEuclideanFunctional (BPS 2010 circle packing) ──────────
# Face-based circle-packing functional ported from
# CPEuclideanFunctional.java. Reference: Bobenko-Pinkall-Springborn 2010.
test_cp_euclidean_functional.cpp
# ── Phase 9a.2: InversiveDistance (Luo 2004 + Glickenstein 2011) ─────────
# Vertex-based inversive-distance circle-packing functional. No Java
# reference; implemented from the literature. Cross-validated against
# EuclideanCyclicFunctional at the natural initial geometry (u = 0).
test_inversive_distance_functional.cpp
# ── Phase 9a: Newton solvers for the two new circle-packing functionals ──
# Convergence tests for newton_cp_euclidean (analytic Hessian) and
# newton_inversive_distance (FD Hessian).
test_newton_phase9a.cpp
# ── Tier-3 Java cross-validation: Lawson square-tiled HyperIdeal ─────────
# Low-level half-edge genus-2 generator + golden-vector convergence.
test_lawson_hyperideal.cpp
# ── Pn projective-metric substrate (de.jreality.math.Pn port) ───────────
# Inner product / norm / distance / geodesic interpolation in
# Euclidean, Elliptic, Hyperbolic signatures.
test_pn_geometry.cpp
# ── Phase 8b-Lite: CGAL entry wrappers for the 4 non-Euclidean modes ─────
# Spherical, HyperIdeal, CircleP-Euclidean, Inversive-Distance via
# <CGAL/Discrete_*.h> public API + Conformal_layout.h wrapper.
test_cgal_phase8b_lite.cpp
)
target_include_directories(conformallab_cgal_tests SYSTEM PRIVATE
${CMAKE_SOURCE_DIR}/deps/eigen-3.4.0
${CMAKE_SOURCE_DIR}/deps/CGAL-6.1.1/include
${CMAKE_SOURCE_DIR}/deps/single_includes
${Boost_INCLUDE_DIRS}
)
target_include_directories(conformallab_cgal_tests PRIVATE
${CMAKE_SOURCE_DIR}/include
)
target_compile_definitions(conformallab_cgal_tests PRIVATE
CGAL_DISABLE_GMP
CGAL_DISABLE_MPFR
# Data directory — absolute path to code/data/ at build time.
# Used by tests that load real mesh files (cathead.obj, brezel2.obj, …).
CONFORMALLAB_DATA_DIR="${CMAKE_SOURCE_DIR}/data"
)
# Suppress warnings from CGAL/Boost headers
target_compile_options(conformallab_cgal_tests PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wno-unused-parameter>
)
# Fast test-build mode (lever #10): -O0 -g overrides the inherited
# Release-mode -O3 + -DNDEBUG. Applies only to this test target.
if(CONFORMALLAB_FAST_TEST_BUILD)
target_compile_options(conformallab_cgal_tests PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-O0 -g -UNDEBUG>
)
endif()
# ── Low-memory build mode (for RAM-constrained CI runners, e.g. Raspberry Pi) ──
#
# Problem: CGAL + Eigen at -O3 drives cc1plus peak RAM to ~600-800 MB per
# Unity compilation unit on ARM64 Linux. A 1600 MB container limit with a
# batch of 4 files per unit causes OOM-kill during the build.
#
# This flag enables three orthogonal memory-saving measures:
#
# 1. -O0 (no debug info): drops cc1plus backend RAM by ~60-70 %.
# Optimizer passes (inlining, register allocation, constant propagation)
# dominate the backend. At -O0 they are entirely skipped → peak per
# TU falls from ~700 MB to ~150-200 MB on ARM64.
# We omit -g deliberately: debug info adds ~30-40 % object-file size
# and increases linker RSS. CI needs "does it compile + do tests pass",
# not debuggability.
#
# 2. PCH OFF: the precompiled header itself consumes ~200 MB to compile
# and is re-read by every TU. Disabling it saves the one-time PCH
# compilation cost; each TU re-parses CGAL headers, but at -O0 this
# is fast.
#
# 3. UNITY_BUILD_BATCH_SIZE=1: one source file per unity unit. Removes
# the "4 files × CGAL parse cost" multiplier; each cc1plus process
# only sees one file worth of templates.
#
# 4. Linker memory flag (GCC/Clang only): --no-keep-memory tells GNU ld
# to release symbol table memory after each input file instead of
# keeping it for cross-reference. Reduces linker RSS by 15-25 % at
# the cost of slightly longer link time.
#
# Activate with:
# cmake -S code -B build -DWITH_CGAL_TESTS=ON \
# -DCONFORMALLAB_LOW_MEMORY_BUILD=ON
# Expected peak per cc1plus: ~150-200 MB → fits in 1800-2000 MB container.
# Test runtime is 2-4× slower than Release (CGAL traversals unoptimized),
# but correctness is unaffected.
option(CONFORMALLAB_LOW_MEMORY_BUILD
"Build CGAL tests with -O0, no PCH, UNITY_BATCH_SIZE=1 for RAM-constrained CI." OFF)
if(CONFORMALLAB_LOW_MEMORY_BUILD)
message(STATUS "CONFORMALLAB_LOW_MEMORY_BUILD active: -O0, no PCH, unity batch 1.")
# 1. -O0, no debug info
target_compile_options(conformallab_cgal_tests PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-O0 -UNDEBUG>
)
# 2. PCH off — force-override the option so the block below is skipped
set(CONFORMALLAB_USE_PCH OFF CACHE BOOL "" FORCE)
# 3. Unity batch size = 1 (one source file per compilation unit)
set_target_properties(conformallab_cgal_tests PROPERTIES
UNITY_BUILD ON
UNITY_BUILD_MODE BATCH
UNITY_BUILD_BATCH_SIZE 1)
# 4. Linker memory hint (GNU ld / lld)
target_link_options(conformallab_cgal_tests PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-Wl,--no-keep-memory>)
endif()
target_link_libraries(conformallab_cgal_tests PRIVATE GTest::gtest_main)
# ── Compile-time speed-up: precompiled headers ───────────────────────────────
#
# The CGAL+Eigen template soup dominates every TU in this target:
# measured at 5.9 s per minimal "include <CGAL/Discrete_conformal_map.h>"
# TU on Apple M1. A shared PCH absorbs that cost once, slashing the
# total wall-clock from ~78 s (j8) to ~25 s (3×).
#
# Opt-out with -DCONFORMALLAB_USE_PCH=OFF if the PCH itself misbehaves
# (e.g. older toolchains that don't share PCH across translation units
# reliably) — falls back to the historical "every TU re-parses CGAL"
# build mode.
option(CONFORMALLAB_USE_PCH
"Enable precompiled headers for the CGAL test target." ON)
if(CONFORMALLAB_USE_PCH)
# Per-target Unity Build property takes precedence over the global
# CMAKE_UNITY_BUILD; honour CONFORMALLAB_DEV_BUILD's preference here
# so `-DCONFORMALLAB_DEV_BUILD=ON` truly turns Unity Build off for
# incremental-rebuild workflows.
if(NOT CONFORMALLAB_DEV_BUILD)
set_target_properties(conformallab_cgal_tests PROPERTIES
# Unity-builds amortise the per-TU CGAL+Eigen header cost
# across several tests in the same compile. Batch size 4
# keeps gtest's TEST(...) macros + per-file `using
# namespace …` from colliding while still cutting parser
# cost ~4×.
UNITY_BUILD ON
UNITY_BUILD_MODE BATCH
UNITY_BUILD_BATCH_SIZE 4)
endif()
target_precompile_headers(conformallab_cgal_tests PRIVATE
# CGAL headers that every test transitively includes.
<CGAL/Surface_mesh.h>
<CGAL/Simple_cartesian.h>
<CGAL/Kernel_traits.h>
<CGAL/boost/graph/iterator.h>
<CGAL/Polygon_mesh_processing/triangulate_faces.h>
# Eigen blocks that drive the slowest template instantiations
# (SelfAdjointEigenSolver<Matrix<2,2>>, ColPivHouseholderQR<
# Matrix<complex,3,3>>, sparse Cholesky + QR fallback).
<Eigen/Dense>
<Eigen/Sparse>
<Eigen/SparseCholesky>
<Eigen/SparseQR>
# GoogleTest itself; every test includes it.
<gtest/gtest.h>
# std headers that appear in every test.
<vector>
<string>
<cmath>
<complex>
)
endif()
include(GoogleTest)
gtest_discover_tests(conformallab_cgal_tests
TEST_PREFIX "cgal."
DISCOVERY_TIMEOUT 60
)