Files
ViewDesignEngine/tests/fuzz/fuzz_sdf.cpp
T
茂之钳 7b76689ea1
CI / Build & Test (push) Failing after 41s
CI / Release Build (push) Failing after 30s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
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)
2026-07-27 01:10:58 +08:00

272 lines
9.0 KiB
C++
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.
/// @file fuzz_sdf.cpp
/// @brief SDF 随机CSG树模糊测试 + 持续运行模式
///
/// 测试策略:
/// 1. 随机生成深度 CSG 树(最大深度 6)
/// 2. 随机选择操作符(union/intersection/difference/smooth variants
/// 3. 随机选择图元(sphere/box/cylinder/torus/capsule
/// 4. 随机应用域变形(translate/rotate/scale/twist/bend
/// 5. 随机采样点评估并验证结果
/// 6. 持续运行模式(命令行参数 -1 表示无限循环)
#include <gtest/gtest.h>
#include "vde/sdf/sdf_primitives.h"
#include "vde/sdf/sdf_operations.h"
#include "vde/sdf/sdf_tree.h"
#include <cmath>
#include <random>
#include <vector>
#include <string>
#include <cstdlib>
using namespace vde::sdf;
using namespace vde::core;
// ═══════════════════════════════════════════════
// SDF 随机种子
// ═══════════════════════════════════════════════
static std::mt19937 s_rng(42);
// ═══════════════════════════════════════════════
// 随机参数生成器
// ═══════════════════════════════════════════════
namespace {
/// 随机双精度 [lo, hi]
double rand_range(double lo, double hi) {
std::uniform_real_distribution<double> dist(lo, hi);
return dist(s_rng);
}
/// 随机 Point3D,范围 [lo, hi]
Point3D rand_point(double lo, double hi) {
return Point3D(rand_range(lo, hi), rand_range(lo, hi), rand_range(lo, hi));
}
/// 随机选择操作类型(CSG 布尔)
SdfOp rand_csg_op() {
static const SdfOp ops[] = {
SdfOp::Union, SdfOp::Intersection, SdfOp::Difference,
SdfOp::SmoothUnion, SdfOp::SmoothIntersection, SdfOp::SmoothDifference
};
return ops[std::uniform_int_distribution<int>(0, 5)(s_rng)];
}
/// 随机选择图元类型
SdfOp rand_primitive_op() {
static const SdfOp ops[] = {
SdfOp::Sphere, SdfOp::Box, SdfOp::Cylinder,
SdfOp::Torus, SdfOp::Capsule
};
return ops[std::uniform_int_distribution<int>(0, 4)(s_rng)];
}
/// 随机选择域变形类型
SdfOp rand_transform_op() {
static const SdfOp ops[] = {
SdfOp::Translate, SdfOp::Rotate, SdfOp::Scale,
SdfOp::Twist, SdfOp::Bend
};
return ops[std::uniform_int_distribution<int>(0, 4)(s_rng)];
}
} // anonymous
// ═══════════════════════════════════════════════
// 随机 CSG 树构造
// ═══════════════════════════════════════════════
/// 创建随机图元节点
SdfNodePtr rand_primitive() {
SdfOp op = rand_primitive_op();
auto node = std::make_shared<SdfNode>(op);
switch (op) {
case SdfOp::Sphere:
node->params.radius = rand_range(0.1, 5.0);
break;
case SdfOp::Box:
node->params.extents = rand_point(0.1, 3.0);
break;
case SdfOp::Cylinder:
node->params.radius = rand_range(0.1, 3.0);
node->params.height = rand_range(0.2, 6.0);
break;
case SdfOp::Torus:
node->params.major_radius = rand_range(0.5, 4.0);
node->params.minor_radius = rand_range(0.1, 1.5);
break;
case SdfOp::Capsule:
node->params.pt_a = Point3D(0, -rand_range(0.5, 3.0), 0);
node->params.pt_b = Point3D(0, rand_range(0.5, 3.0), 0);
node->params.radius = rand_range(0.1, 1.0);
break;
default:
break;
}
return node;
}
/// 创建随机 CSG 布尔节点(递归)
SdfNodePtr rand_csg_tree(int depth, int max_depth) {
if (depth >= max_depth) {
return rand_primitive();
}
SdfOp op = rand_csg_op();
auto left = rand_csg_tree(depth + 1, max_depth);
auto right = rand_csg_tree(depth + 1, max_depth);
auto node = std::make_shared<SdfNode>(op);
node->children.push_back(left);
node->children.push_back(right);
if (op == SdfOp::SmoothUnion || op == SdfOp::SmoothIntersection ||
op == SdfOp::SmoothDifference) {
node->params.blend_k = rand_range(0.05, 0.5);
}
return node;
}
/// 随机应用域变形包装
SdfNodePtr wrap_transform(SdfNodePtr child) {
if (rand_range(0.0, 1.0) < 0.3) {
SdfOp op = rand_transform_op();
auto node = std::make_shared<SdfNode>(op);
node->children.push_back(child);
switch (op) {
case SdfOp::Translate:
node->params.translate_offset = rand_point(-2.0, 2.0);
break;
case SdfOp::Rotate:
node->params.amount = rand_range(-M_PI, M_PI);
break;
case SdfOp::Scale:
node->params.scale_factors = rand_point(0.5, 2.0);
break;
case SdfOp::Twist:
node->params.amount = rand_range(-0.5, 0.5);
break;
case SdfOp::Bend:
node->params.amount = rand_range(-0.3, 0.3);
break;
default:
break;
}
return node;
}
return child;
}
// ═══════════════════════════════════════════════
// 模糊测试用例
// ═══════════════════════════════════════════════
TEST(FuzzSdf, RandomCSGTree_Evaluate) {
// 生成多个随机树并在多个点处求值
for (int i = 0; i < 50; ++i) {
auto tree = rand_csg_tree(0, 4);
// 可选变形包装
tree = wrap_transform(tree);
for (int j = 0; j < 30; ++j) {
Point3D p = rand_point(-5.0, 5.0);
EXPECT_NO_THROW({
double v = evaluate(tree, p);
// 求值结果必须是有限数(不应是 NaN 或 Inf)
EXPECT_TRUE(std::isfinite(v));
});
}
}
}
TEST(FuzzSdf, RandomCSGTree_DeepTree) {
// 深度 CSG 树(depth 6 → 最多 2^6 = 64 个叶子节点)
for (int i = 0; i < 20; ++i) {
auto tree = rand_csg_tree(0, 6);
EXPECT_NO_THROW({
auto bbox = estimate_bbox(tree, 0.5);
EXPECT_TRUE(std::isfinite(bbox.min.x()));
EXPECT_TRUE(std::isfinite(bbox.max.x()));
Point3D p = rand_point(-10.0, 10.0);
double v = evaluate(tree, p);
EXPECT_TRUE(std::isfinite(v));
});
}
}
TEST(FuzzSdf, RandomCSGTree_Bounds) {
// 验证 estimate_bounds 对所有随机树返回合理的值
for (int i = 0; i < 30; ++i) {
auto tree = rand_csg_tree(0, 3);
tree = wrap_transform(tree);
EXPECT_NO_THROW({
auto half = estimate_bounds(tree, 1.0);
EXPECT_GE(half.x(), 0.0);
EXPECT_GE(half.y(), 0.0);
EXPECT_GE(half.z(), 0.0);
EXPECT_TRUE(std::isfinite(half.x()));
EXPECT_TRUE(std::isfinite(half.y()));
EXPECT_TRUE(std::isfinite(half.z()));
});
}
}
TEST(FuzzSdf, RandomTree_Visit) {
// 验证 visit() 对所有随机树都不崩溃
for (int i = 0; i < 30; ++i) {
auto tree = rand_csg_tree(0, 4);
tree = wrap_transform(tree);
EXPECT_NO_THROW({
int count = 0;
tree->visit([&](const SdfNode& /*n*/) { ++count; });
EXPECT_GT(count, 0);
});
}
}
// ═══════════════════════════════════════════════
// 持续运行模式(-1 = 无限循环)
// 用法: ./fuzz_sdf --fuzz_forever
// ═══════════════════════════════════════════════
TEST(FuzzSdf, ContinuousFuzz) {
// 检查环境变量 VDE_FUZZ_FOREVER=1 启用持续模式
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 tree = rand_csg_tree(0, std::uniform_int_distribution<int>(2, 5)(s_rng));
tree = wrap_transform(tree);
Point3D p = rand_point(-10.0, 10.0);
double v = evaluate(tree, p);
ASSERT_TRUE(std::isfinite(v))
<< "iteration=" << iteration
<< " 非有限 SDF 值: " << v;
// 每 10000 次打印一次进度
if (iteration % 10000 == 0) {
auto bbox = estimate_bbox(tree, 1.0);
std::cout << "[FuzzSdf] iteration=" << iteration
<< " value=" << v
<< " bbox=[" << bbox.min.x() << "," << bbox.max.x() << "]"
<< std::endl;
}
}
}