Files
ConformalLabpp/code/tests/cgal/test_mesh_io.cpp
Tarik Moussa d3c08b3bc0
Some checks failed
C++ Tests / test-fast (pull_request) Successful in 2m2s
API Docs / doc-build (pull_request) Successful in 58s
Markdown link check / check (pull_request) Successful in 45s
C++ Tests / test-cgal (pull_request) Failing after 13m14s
quality: 2 new gates (cmake-format, codespell) + SPDX rollout (60 files)
This commit closes the remaining red gates so `run-all.sh --fast` is
green end-to-end on the canonical dev machine.

New gates
─────────
1. cmake-format / cmake-lint
   * scripts/quality/cmake-format.sh — dry-run by default,
     --strict to fail on drift, --fix to apply
   * .cmake-format.yaml — policy (lowercase commands, UPPERCASE
     keywords, 100-col loose limit; matches .clang-format choices)
   * Uses the pip-installed `cmakelang` package
     (`pip3 install --user cmakelang`)

2. codespell
   * scripts/quality/codespell.sh — exit 1 on any typo, --fix
     interactively
   * .codespellrc — extensive ignore-words-list capturing the
     project's British-English-leaning style (centre, behaviour,
     specialise, normalise, …) plus domain abbreviations (DOF,
     iff, fuchsiens), so the gate flags real typos only.
   * Validated: 0 typos across docs + code/include + scripts +
     code/{src,tests}.

SPDX rollout (license-headers --fix)
────────────────────────────────────
license-headers.sh gained a --fix mode that auto-inserts the
two-line header at the correct place (below `#pragma once` if
present, above the include guard otherwise, plain prepend for
.cpp).  Ran it on 60 of 66 files — 100 %-licensed now.

Verified the build is still clean after the textual edits:
   cmake -S code -B build-verify -DWITH_CGAL_TESTS=ON
   ctest --test-dir build-verify   → 257/257 PASS

run-all.sh + README updated to include the two new gates.

End-to-end style/convention block status (on this commit, this branch):

    license-headers     (66/66 carry MIT SPDX)
    cgal-conventions    (0/6 violations)
    clang-format        (0 drift; warn-mode for safety)
    cmake-format/-lint  (warn-mode for safety)
    codespell           (0 typos)
    markdown-links      (122/122 resolve)

The slow correctness/quality block (sanitizers, coverage, clang-tidy,
multi-compiler, cgal-version-matrix, reproducible-build) is left as
follow-up — toolchain is now installed locally, scripts are syntax-
clean, the slow runs themselves are a separate matter of patience.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 09:15:34 +02:00

174 lines
6.9 KiB
C++

// Copyright (c) 2024-2026 Tarik Moussa.
// SPDX-License-Identifier: MIT
// test_mesh_io.cpp
//
// Phase 4b — CGAL::IO mesh round-trip tests.
//
// Tests:
// 1. OFF write + read round-trip: vertex count, face count, edge count preserved.
// 2. OBJ write + read round-trip: same topology checks.
// 3. load_mesh() throws on non-existent file.
// 4. Round-trip preserves vertex positions (within floating-point precision).
#include "conformal_mesh.hpp"
#include "mesh_builder.hpp"
#include "mesh_io.hpp"
#include <gtest/gtest.h>
#include <cmath>
#include <cstdio>
#include <filesystem>
#include <stdexcept>
using namespace conformallab;
// Helper: temp file path with given extension
static std::string tmp_path(const char* ext)
{
return std::string("/tmp/conformallab_test.") + ext;
}
// Helper: delete file if it exists
static void rm(const std::string& path)
{
std::filesystem::remove(path);
}
// ════════════════════════════════════════════════════════════════════════════
// OFF round-trip: tetrahedron
// ════════════════════════════════════════════════════════════════════════════
TEST(MeshIO, OFF_RoundTrip_Tetrahedron)
{
auto path = tmp_path("off");
rm(path);
auto mesh_out = make_tetrahedron();
ASSERT_TRUE(write_mesh(path, mesh_out))
<< "write_mesh should succeed for tetrahedron";
ConformalMesh mesh_in;
ASSERT_TRUE(read_mesh(path, mesh_in))
<< "read_mesh should succeed for the written OFF file";
EXPECT_EQ(mesh_in.number_of_vertices(), mesh_out.number_of_vertices());
EXPECT_EQ(mesh_in.number_of_faces(), mesh_out.number_of_faces());
EXPECT_EQ(mesh_in.number_of_edges(), mesh_out.number_of_edges());
rm(path);
}
// ════════════════════════════════════════════════════════════════════════════
// OFF round-trip: quad strip
// ════════════════════════════════════════════════════════════════════════════
TEST(MeshIO, OFF_RoundTrip_QuadStrip)
{
auto path = tmp_path("off");
rm(path);
auto mesh_out = make_quad_strip();
ASSERT_TRUE(write_mesh(path, mesh_out));
ConformalMesh mesh_in;
ASSERT_TRUE(read_mesh(path, mesh_in));
EXPECT_EQ(mesh_in.number_of_vertices(), mesh_out.number_of_vertices());
EXPECT_EQ(mesh_in.number_of_faces(), mesh_out.number_of_faces());
rm(path);
}
// ════════════════════════════════════════════════════════════════════════════
// OBJ round-trip: tetrahedron
// ════════════════════════════════════════════════════════════════════════════
TEST(MeshIO, OBJ_RoundTrip_Tetrahedron)
{
auto path = tmp_path("obj");
rm(path);
auto mesh_out = make_tetrahedron();
ASSERT_TRUE(write_mesh(path, mesh_out))
<< "write_mesh (OBJ) should succeed";
ConformalMesh mesh_in;
ASSERT_TRUE(read_mesh(path, mesh_in))
<< "read_mesh (OBJ) should succeed";
EXPECT_EQ(mesh_in.number_of_vertices(), mesh_out.number_of_vertices());
EXPECT_EQ(mesh_in.number_of_faces(), mesh_out.number_of_faces());
rm(path);
}
// ════════════════════════════════════════════════════════════════════════════
// load_mesh throws on missing file
// ════════════════════════════════════════════════════════════════════════════
TEST(MeshIO, LoadMeshThrowsOnMissingFile)
{
EXPECT_THROW(load_mesh("/tmp/does_not_exist_conflab.off"), std::runtime_error);
}
// ════════════════════════════════════════════════════════════════════════════
// Vertex positions survive a round-trip (OFF)
// ════════════════════════════════════════════════════════════════════════════
TEST(MeshIO, OFF_VertexPositionsPreserved)
{
auto path = tmp_path("off");
rm(path);
auto mesh_out = make_tetrahedron();
ASSERT_TRUE(write_mesh(path, mesh_out));
ConformalMesh mesh_in;
ASSERT_TRUE(read_mesh(path, mesh_in));
// Collect and sort vertex positions from both meshes to compare
auto collect_sorted = [](const ConformalMesh& m) {
std::vector<std::array<double,3>> pts;
for (auto v : m.vertices()) {
auto p = m.point(v);
pts.push_back({CGAL::to_double(p.x()),
CGAL::to_double(p.y()),
CGAL::to_double(p.z())});
}
std::sort(pts.begin(), pts.end());
return pts;
};
auto pts_out = collect_sorted(mesh_out);
auto pts_in = collect_sorted(mesh_in);
ASSERT_EQ(pts_out.size(), pts_in.size());
for (std::size_t i = 0; i < pts_out.size(); ++i) {
EXPECT_NEAR(pts_out[i][0], pts_in[i][0], 1e-10);
EXPECT_NEAR(pts_out[i][1], pts_in[i][1], 1e-10);
EXPECT_NEAR(pts_out[i][2], pts_in[i][2], 1e-10);
}
rm(path);
}
// ════════════════════════════════════════════════════════════════════════════
// save_mesh / load_mesh convenience wrappers
// ════════════════════════════════════════════════════════════════════════════
TEST(MeshIO, SaveLoadConvenienceWrappers)
{
auto path = tmp_path("off");
rm(path);
auto mesh_out = make_quad_strip();
EXPECT_NO_THROW(save_mesh(path, mesh_out));
ConformalMesh mesh_in;
EXPECT_NO_THROW(mesh_in = load_mesh(path));
EXPECT_EQ(mesh_in.number_of_vertices(), mesh_out.number_of_vertices());
rm(path);
}