fix(coverage+validation): C2/C3 script gate, V1/V2/V4 JSON/XML errors, I2/I3/I4 tests

C2: Fix coverage.sh — remove || true from lcov capture/extract steps so real
    lcov failures are visible; empty coverage.info now exits with code 3.
C3: Add coverage gate to quality-gates CI job (SKIP_COVERAGE_GATE=1 ramp-up
    mode until I5 is resolved — fast suite covers ~9.6% not 80%). Thresholds:
    80% line / 70% branch / 90% function (agreed 2026-05-31).

V1: Wrap JSON parse + field extraction in try/catch — nlohmann parse_error and
    type_error now surface as std::runtime_error with the file path.
V2: Wrap stoi/stod in XML Solver parser — missing/non-numeric attributes throw
    std::runtime_error instead of leaking std::invalid_argument.
V4: Validate required JSON keys (dof_vector, solver, solver.*) before access —
    missing field produces a clear named-field error message.

I2: 6 serialization negative tests (missing file, malformed JSON, missing
    dof_vector, missing solver block, missing XML file, non-numeric XML attr).
I3: load_mesh throws on non-triangulated (quad) mesh — covers the
    is_triangle_mesh guard that was previously untested.
I4: spherical_hessian throws on edge DOFs — covers the logic_error guard.

290/290 tests pass (+8 new).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-31 14:43:00 +02:00
parent 51d9844f7a
commit a5718c0326
6 changed files with 274 additions and 45 deletions

View File

@@ -206,3 +206,31 @@ TEST(SphericalHessian, FDCheck_MixedPinnedVertices)
EXPECT_TRUE(hessian_check_spherical(mesh, x, maps))
<< "FD Hessian check failed for mixed pinned/variable vertices";
}
// ════════════════════════════════════════════════════════════════════════════
// I4: spherical_hessian throws on edge DOFs
//
// The spherical Hessian only implements the vertex-block cotangent Laplacian;
// if any edge has a free DOF index (e_idx >= 0) it must fail loudly rather
// than returning a silently rank-deficient matrix.
// ════════════════════════════════════════════════════════════════════════════
TEST(SphericalHessian, ThrowsOnEdgeDOF)
{
// Build a tetrahedron and assign one edge as a free DOF.
auto mesh = make_tetrahedron();
auto maps = setup_spherical_maps(mesh);
compute_lambda0_from_mesh(mesh, maps); // spherical variant (A2 rename pending)
int idx = 0;
for (auto v : mesh.vertices())
maps.v_idx[v] = idx++;
const int n_v = idx;
// Give one edge a free DOF index to trigger the guard.
auto e0 = *mesh.edges().begin();
maps.e_idx[e0] = n_v; // first free edge DOF
std::vector<double> x(static_cast<std::size_t>(n_v + 1), 0.0);
EXPECT_THROW(spherical_hessian(mesh, x, maps), std::logic_error);
}