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

@@ -24,6 +24,7 @@
#include "serialization.hpp"
#include <gtest/gtest.h>
#include <cmath>
#include <fstream>
#include <vector>
#include <filesystem>
@@ -370,3 +371,67 @@ TEST(Serialization, XML_RoundTrip)
std::filesystem::remove(path);
}
// ════════════════════════════════════════════════════════════════════════════
// I2: serialization throws on malformed / schema-violating input
//
// These tests cover the throw paths in load_result_json / load_result_xml
// that were previously untested (I2 in the test-coverage audit).
// ════════════════════════════════════════════════════════════════════════════
TEST(Serialization, LoadResultJson_ThrowsOnMissingFile)
{
EXPECT_THROW(load_result_json("/tmp/does_not_exist_conflab.json"),
std::runtime_error);
}
TEST(Serialization, LoadResultJson_ThrowsOnMalformedJson)
{
const std::string path = "/tmp/conflab_bad.json";
{ std::ofstream ofs(path); ofs << "{not valid json!!!"; }
EXPECT_THROW(load_result_json(path), std::runtime_error);
std::filesystem::remove(path);
}
TEST(Serialization, LoadResultJson_ThrowsOnMissingDofVector)
{
// V4: a valid JSON object but without the required "dof_vector" key.
const std::string path = "/tmp/conflab_nodof.json";
{ std::ofstream ofs(path); ofs << R"({"geometry":"euclidean"})"; }
EXPECT_THROW(load_result_json(path), std::runtime_error);
std::filesystem::remove(path);
}
TEST(Serialization, LoadResultJson_ThrowsOnMissingSolverField)
{
// V4: has dof_vector but solver block is absent when res != nullptr.
const std::string path = "/tmp/conflab_nosolver.json";
{ std::ofstream ofs(path); ofs << R"({"dof_vector":[0.1,0.2]})"; }
NewtonResult res;
EXPECT_THROW(load_result_json(path, &res), std::runtime_error);
std::filesystem::remove(path);
}
TEST(Serialization, LoadResultXml_ThrowsOnMissingFile)
{
EXPECT_THROW(load_result_xml("/tmp/does_not_exist_conflab.xml"),
std::runtime_error);
}
TEST(Serialization, LoadResultXml_ThrowsOnMalformedSolverAttribute)
{
// V2: non-numeric attribute causes stoi/stod error that must surface
// as runtime_error, not std::invalid_argument.
const std::string path = "/tmp/conflab_badxml.xml";
{
std::ofstream ofs(path);
ofs << R"(<?xml version="1.0"?>
<ConformalResult geometry="euclidean" vertices="4" faces="4">
<Solver converged="true" iterations="not_a_number" grad_inf_norm="1e-9"/>
<DOFVector n="1">0.0</DOFVector>
</ConformalResult>)";
}
NewtonResult res;
EXPECT_THROW(load_result_xml(path, &res), std::runtime_error);
std::filesystem::remove(path);
}