feat(v11): CI/CD + regression tests + fuzzing + benchmarks + code quality
v11.1 — Test Infrastructure (14 files): - .github/workflows/ci.yml: Ubuntu 22.04 + GCC 11, auto ctest on push - tests/regression/: degenerate geometry, extreme coords, thin wall, large models - tests/fuzz/: SDF random CSG, random booleans, format round-trip, continuous mode - tests/benchmark/: boolean perf, MC perf, IO perf benchmarks - docs/benchmark-report.md: benchmark template v11.2 — Code Quality + Docs: - .clang-tidy: 15 check categories, 22 exclusions - .github/workflows/static-analysis.yml: clang-tidy CI scan - CODEOWNERS, SECURITY.md - docs: API overview, testing guide, contributing guide - README: module capability overview table Pending: binary formats + curve projection (retrying)
This commit is contained in:
+200
-37
@@ -1,59 +1,222 @@
|
||||
/// @file fuzz_boolean.cpp
|
||||
/// @brief B-Rep 布尔随机参数模糊测试 + 持续运行模式
|
||||
///
|
||||
/// 测试策略:
|
||||
/// 1. 随机尺寸的盒子/圆柱/球体对做布尔运算
|
||||
/// 2. 随机偏移后的布尔运算
|
||||
/// 3. 连续布尔链式模糊测试
|
||||
/// 4. 验证结果的合法性
|
||||
/// 5. 持续运行模式
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/brep/brep.h"
|
||||
#include "vde/brep/brep_boolean.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include "vde/brep/brep_validate.h"
|
||||
#include "vde/brep/brep_heal.h"
|
||||
#include "vde/core/point.h"
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace vde::brep;
|
||||
using namespace vde::core;
|
||||
|
||||
// ── B-Rep fuzz: random sized boxes with boolean operations ──
|
||||
static std::mt19937 s_rng(42);
|
||||
|
||||
TEST(FuzzBoolean, RandomBoxBooleans) {
|
||||
std::mt19937 rng(42);
|
||||
std::uniform_real_distribution<double> size_dist(1.0, 5.0);
|
||||
std::uniform_real_distribution<double> offset_dist(-2.0, 2.0);
|
||||
// ── 随机参数生成器 ──
|
||||
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
double s1 = size_dist(rng);
|
||||
double s2 = size_dist(rng);
|
||||
double ox = offset_dist(rng);
|
||||
double oy = offset_dist(rng);
|
||||
double oz = offset_dist(rng);
|
||||
// NOTE: ox, oy, oz computed for future translate API use
|
||||
(void)ox; (void)oy; (void)oz;
|
||||
namespace {
|
||||
|
||||
auto box1 = make_box(s1, s1, s1);
|
||||
auto box2 = make_box(s2, s2, s2);
|
||||
double rand_range(double lo, double hi) {
|
||||
return std::uniform_real_distribution<double>(lo, hi)(s_rng);
|
||||
}
|
||||
|
||||
// Test that operations don't crash
|
||||
auto u = brep_union(box1, box2);
|
||||
auto inter = brep_intersection(box1, box2);
|
||||
auto diff = brep_difference(box1, box2);
|
||||
BrepModel rand_box() {
|
||||
return make_box(rand_range(0.5, 10.0),
|
||||
rand_range(0.5, 10.0),
|
||||
rand_range(0.5, 10.0));
|
||||
}
|
||||
|
||||
// Basic sanity: results should have finite bounds
|
||||
EXPECT_TRUE(std::isfinite(u.bounds().min().x()));
|
||||
EXPECT_TRUE(std::isfinite(inter.bounds().min().x()));
|
||||
EXPECT_TRUE(std::isfinite(diff.bounds().min().x()));
|
||||
BrepModel rand_cylinder() {
|
||||
return make_cylinder(rand_range(0.2, 5.0),
|
||||
rand_range(0.5, 10.0),
|
||||
static_cast<int>(rand_range(8, 64)));
|
||||
}
|
||||
|
||||
BrepModel rand_sphere() {
|
||||
return make_sphere(rand_range(0.3, 5.0),
|
||||
static_cast<int>(rand_range(8, 32)),
|
||||
static_cast<int>(rand_range(4, 16)));
|
||||
}
|
||||
|
||||
BrepModel rand_primitive() {
|
||||
int choice = static_cast<int>(rand_range(0, 3));
|
||||
switch (choice) {
|
||||
case 0: return rand_box();
|
||||
case 1: return rand_cylinder();
|
||||
default: return rand_sphere();
|
||||
}
|
||||
}
|
||||
|
||||
// ── B-Rep fuzz: self-operations (A∪A, A∩A, A\A) ──
|
||||
} // anonymous
|
||||
|
||||
TEST(FuzzBoolean, SelfOperations) {
|
||||
// ═══════════════════════════════════════════════
|
||||
// 随机盒子布尔测试
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(FuzzBoolean, RandomBoxUnion) {
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
auto a = rand_box();
|
||||
auto b = rand_box();
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto u = brep_union(a, b);
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
|
||||
// 结果不应为空体
|
||||
EXPECT_GT(u.num_vertices(), 0u);
|
||||
|
||||
// 验证
|
||||
auto result = validate(u);
|
||||
(void)result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FuzzBoolean, RandomBoxIntersection) {
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
auto a = rand_box();
|
||||
auto b = rand_box();
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto inter = brep_intersection(a, b);
|
||||
EXPECT_TRUE(inter.is_valid());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FuzzBoolean, RandomBoxDifference) {
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
auto a = rand_box();
|
||||
auto b = rand_box();
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto diff = brep_difference(a, b);
|
||||
EXPECT_TRUE(diff.is_valid());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 随机基本体布尔混合测试
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(FuzzBoolean, RandomMixedPrimitives) {
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
auto a = rand_primitive();
|
||||
auto b = rand_primitive();
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto u = brep_union(a, b);
|
||||
EXPECT_TRUE(u.is_valid()) << "union failed at iteration " << i;
|
||||
|
||||
auto inter = brep_intersection(a, b);
|
||||
EXPECT_TRUE(inter.is_valid()) << "intersection failed at iteration " << i;
|
||||
|
||||
auto diff = brep_difference(a, b);
|
||||
EXPECT_TRUE(diff.is_valid()) << "difference failed at iteration " << i;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 随机布尔链(连续多个布尔操作)
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(FuzzBoolean, RandomBooleanChain) {
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
double s = 1.0 + i * 0.2;
|
||||
auto box = make_box(s, s, s);
|
||||
EXPECT_NO_THROW({
|
||||
auto result = rand_primitive();
|
||||
int chain_len = static_cast<int>(rand_range(2, 6));
|
||||
|
||||
// Self-union should be valid
|
||||
auto self_u = brep_union(box, box);
|
||||
EXPECT_TRUE(self_u.is_valid());
|
||||
for (int j = 0; j < chain_len; ++j) {
|
||||
auto other = rand_primitive();
|
||||
int op_choice = static_cast<int>(rand_range(0, 3));
|
||||
switch (op_choice) {
|
||||
case 0: result = brep_union(result, other); break;
|
||||
case 1: result = brep_intersection(result, other); break;
|
||||
default: result = brep_difference(result, other); break;
|
||||
}
|
||||
ASSERT_TRUE(result.is_valid())
|
||||
<< "chain failed at step " << j << " of chain " << i;
|
||||
}
|
||||
|
||||
// Self-difference should be valid
|
||||
auto self_d = brep_difference(box, box);
|
||||
EXPECT_TRUE(self_d.is_valid());
|
||||
|
||||
// Self-intersection should be valid
|
||||
auto self_i = brep_intersection(box, box);
|
||||
EXPECT_TRUE(self_i.is_valid());
|
||||
// 最终结果应仍有效
|
||||
EXPECT_TRUE(result.is_valid());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 自交操作模糊测试
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(FuzzBoolean, RandomSelfOperations) {
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
auto body = rand_primitive();
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto u = brep_union(body, body);
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
|
||||
auto inter = brep_intersection(body, body);
|
||||
EXPECT_TRUE(inter.is_valid());
|
||||
|
||||
auto diff = brep_difference(body, body);
|
||||
EXPECT_TRUE(diff.is_valid());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 持续运行模式
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(FuzzBoolean, ContinuousFuzz) {
|
||||
const char* forever = std::getenv("VDE_FUZZ_FOREVER");
|
||||
if (!forever || std::string(forever) != "1") {
|
||||
GTEST_SKIP() << "设置 VDE_FUZZ_FOREVER=1 启用持续模糊测试";
|
||||
}
|
||||
|
||||
int iteration = 0;
|
||||
while (true) {
|
||||
++iteration;
|
||||
auto a = rand_primitive();
|
||||
auto b = rand_primitive();
|
||||
|
||||
// 三项布尔操作都必须成功
|
||||
auto u = brep_union(a, b);
|
||||
ASSERT_TRUE(u.is_valid()) << "union failed at iteration " << iteration;
|
||||
|
||||
auto inter = brep_intersection(a, b);
|
||||
ASSERT_TRUE(inter.is_valid()) << "intersection failed at iteration " << iteration;
|
||||
|
||||
auto diff = brep_difference(a, b);
|
||||
ASSERT_TRUE(diff.is_valid()) << "difference failed at iteration " << iteration;
|
||||
|
||||
// 验证
|
||||
auto vr_u = validate(u);
|
||||
(void)vr_u;
|
||||
auto vr_i = validate(inter);
|
||||
(void)vr_i;
|
||||
|
||||
if (iteration % 1000 == 0) {
|
||||
std::cout << "[FuzzBoolean] iteration=" << iteration
|
||||
<< " union_faces=" << u.num_faces()
|
||||
<< " intersection_faces=" << inter.num_faces()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user