7b76689ea1
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)
300 lines
9.9 KiB
C++
300 lines
9.9 KiB
C++
/// @file fuzz_format.cpp
|
||
/// @brief 格式往返模糊测试(IGES/STEP)+ 持续运行模式
|
||
///
|
||
/// 测试策略:
|
||
/// 1. 随机生成 B-Rep 体 → IGES 导出 → IGES 导入 → 验证一致性
|
||
/// 2. 随机生成 B-Rep 体 → STEP 导出 → STEP 导入 → 验证一致性
|
||
/// 3. 极端尺寸的格式往返
|
||
/// 4. 多体格式往返
|
||
/// 5. 持续运行模式
|
||
|
||
#include <gtest/gtest.h>
|
||
#include "vde/brep/brep.h"
|
||
#include "vde/brep/modeling.h"
|
||
#include "vde/brep/iges_export.h"
|
||
#include "vde/brep/iges_import.h"
|
||
#include "vde/brep/step_export.h"
|
||
#include "vde/brep/step_import.h"
|
||
#include "vde/core/point.h"
|
||
#include <cmath>
|
||
#include <random>
|
||
#include <string>
|
||
#include <cstdlib>
|
||
|
||
using namespace vde::brep;
|
||
using namespace vde::core;
|
||
|
||
static std::mt19937 s_rng(42);
|
||
|
||
namespace {
|
||
|
||
double rand_range(double lo, double hi) {
|
||
return std::uniform_real_distribution<double>(lo, hi)(s_rng);
|
||
}
|
||
|
||
BrepModel rand_body() {
|
||
int choice = static_cast<int>(rand_range(0, 3));
|
||
switch (choice) {
|
||
case 0:
|
||
return make_box(rand_range(0.5, 10.0),
|
||
rand_range(0.5, 10.0),
|
||
rand_range(0.5, 10.0));
|
||
case 1:
|
||
return make_cylinder(rand_range(0.3, 5.0),
|
||
rand_range(0.5, 10.0),
|
||
static_cast<int>(rand_range(8, 32)));
|
||
default:
|
||
return make_sphere(rand_range(0.3, 5.0),
|
||
static_cast<int>(rand_range(8, 24)),
|
||
static_cast<int>(rand_range(4, 12)));
|
||
}
|
||
}
|
||
|
||
} // anonymous
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// IGES 随机往返
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(FuzzFormat, Iges_RandomRoundTrip) {
|
||
for (int i = 0; i < 30; ++i) {
|
||
auto body = rand_body();
|
||
EXPECT_TRUE(body.is_valid());
|
||
|
||
// 导出 IGES
|
||
std::string iges;
|
||
EXPECT_NO_THROW({
|
||
iges = export_iges({body});
|
||
});
|
||
EXPECT_FALSE(iges.empty()) << "IGES export failed at iteration " << i;
|
||
|
||
// 重新导入
|
||
std::vector<BrepModel> loaded;
|
||
EXPECT_NO_THROW({
|
||
loaded = import_iges_from_string(iges);
|
||
});
|
||
EXPECT_FALSE(loaded.empty()) << "IGES import failed at iteration " << i;
|
||
|
||
// 验证导入体
|
||
for (auto& lb : loaded) {
|
||
EXPECT_TRUE(lb.is_valid()) << "imported body invalid at iteration " << i;
|
||
}
|
||
}
|
||
}
|
||
|
||
TEST(FuzzFormat, Iges_MultiBodyRoundTrip) {
|
||
for (int i = 0; i < 15; ++i) {
|
||
int count = static_cast<int>(rand_range(2, 5));
|
||
std::vector<BrepModel> bodies;
|
||
for (int j = 0; j < count; ++j) {
|
||
bodies.push_back(rand_body());
|
||
}
|
||
|
||
std::string iges;
|
||
EXPECT_NO_THROW({ iges = export_iges(bodies); });
|
||
EXPECT_FALSE(iges.empty());
|
||
|
||
std::vector<BrepModel> loaded;
|
||
EXPECT_NO_THROW({ loaded = import_iges_from_string(iges); });
|
||
EXPECT_FALSE(loaded.empty());
|
||
EXPECT_GE(loaded.size(), 1u); // 可能合并或保留多个体
|
||
|
||
for (auto& lb : loaded) {
|
||
EXPECT_TRUE(lb.is_valid());
|
||
}
|
||
}
|
||
}
|
||
|
||
TEST(FuzzFormat, Iges_ExtremeSizes) {
|
||
// 极端尺寸的 IGES 往返
|
||
double sizes[] = {1e-3, 1e-1, 1.0, 10.0, 100.0, 1000.0};
|
||
for (double s : sizes) {
|
||
auto body = make_box(s, s * 0.5, s * 2.0);
|
||
|
||
EXPECT_NO_THROW({
|
||
std::string iges = export_iges({body});
|
||
EXPECT_FALSE(iges.empty());
|
||
|
||
auto loaded = import_iges_from_string(iges);
|
||
EXPECT_FALSE(loaded.empty());
|
||
EXPECT_TRUE(loaded[0].is_valid());
|
||
}) << "failed at size=" << s;
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// STEP 随机往返
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(FuzzFormat, Step_RandomRoundTrip) {
|
||
for (int i = 0; i < 30; ++i) {
|
||
auto body = rand_body();
|
||
EXPECT_TRUE(body.is_valid());
|
||
|
||
std::string step;
|
||
EXPECT_NO_THROW({
|
||
step = export_step({body});
|
||
});
|
||
EXPECT_FALSE(step.empty()) << "STEP export failed at iteration " << i;
|
||
|
||
std::vector<BrepModel> loaded;
|
||
EXPECT_NO_THROW({
|
||
loaded = import_step_from_string(step);
|
||
});
|
||
EXPECT_FALSE(loaded.empty()) << "STEP import failed at iteration " << i;
|
||
|
||
for (auto& lb : loaded) {
|
||
EXPECT_TRUE(lb.is_valid()) << "imported body invalid at iteration " << i;
|
||
}
|
||
}
|
||
}
|
||
|
||
TEST(FuzzFormat, Step_MultiBodyRoundTrip) {
|
||
for (int i = 0; i < 15; ++i) {
|
||
int count = static_cast<int>(rand_range(2, 5));
|
||
std::vector<BrepModel> bodies;
|
||
for (int j = 0; j < count; ++j) {
|
||
bodies.push_back(rand_body());
|
||
}
|
||
|
||
std::string step;
|
||
EXPECT_NO_THROW({ step = export_step(bodies); });
|
||
EXPECT_FALSE(step.empty());
|
||
|
||
std::vector<BrepModel> loaded;
|
||
EXPECT_NO_THROW({ loaded = import_step_from_string(step); });
|
||
EXPECT_FALSE(loaded.empty());
|
||
|
||
for (auto& lb : loaded) {
|
||
EXPECT_TRUE(lb.is_valid());
|
||
}
|
||
}
|
||
}
|
||
|
||
TEST(FuzzFormat, Step_ExtremeSizes) {
|
||
double sizes[] = {1e-3, 1e-1, 1.0, 10.0, 100.0, 1000.0};
|
||
for (double s : sizes) {
|
||
auto body = make_box(s, s * 0.5, s * 2.0);
|
||
|
||
EXPECT_NO_THROW({
|
||
std::string step = export_step({body});
|
||
EXPECT_FALSE(step.empty());
|
||
|
||
auto loaded = import_step_from_string(step);
|
||
EXPECT_FALSE(loaded.empty());
|
||
EXPECT_TRUE(loaded[0].is_valid());
|
||
}) << "failed at size=" << s;
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// 交叉格式往返(IGES → STEP → IGES)
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(FuzzFormat, CrossFormat_IGES_to_STEP) {
|
||
for (int i = 0; i < 15; ++i) {
|
||
auto body = rand_body();
|
||
|
||
EXPECT_NO_THROW({
|
||
// IGES export → STEP import
|
||
std::string iges = export_iges({body});
|
||
auto from_iges = import_iges_from_string(iges);
|
||
EXPECT_FALSE(from_iges.empty());
|
||
|
||
// STEP export → IGES import
|
||
std::string step = export_step({body});
|
||
auto from_step = import_step_from_string(step);
|
||
EXPECT_FALSE(from_step.empty());
|
||
|
||
// Both should be valid
|
||
EXPECT_TRUE(from_iges[0].is_valid());
|
||
EXPECT_TRUE(from_step[0].is_valid());
|
||
});
|
||
}
|
||
}
|
||
|
||
TEST(FuzzFormat, CrossFormat_STEP_to_IGES) {
|
||
for (int i = 0; i < 15; ++i) {
|
||
auto body = rand_body();
|
||
|
||
EXPECT_NO_THROW({
|
||
// STEP → IGES
|
||
std::string step = export_step({body});
|
||
auto from_step = import_step_from_string(step);
|
||
ASSERT_FALSE(from_step.empty());
|
||
|
||
std::string iges = export_iges(from_step);
|
||
auto from_iges = import_iges_from_string(iges);
|
||
EXPECT_FALSE(from_iges.empty());
|
||
EXPECT_TRUE(from_iges[0].is_valid());
|
||
});
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// 布尔运算后的格式往返
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(FuzzFormat, BooleanThenRoundTrip) {
|
||
for (int i = 0; i < 10; ++i) {
|
||
auto a = rand_body();
|
||
auto b = rand_body();
|
||
|
||
EXPECT_NO_THROW({
|
||
// 先做布尔运算
|
||
auto u = brep_union(a, b);
|
||
|
||
// IGES 往返
|
||
std::string iges = export_iges({u});
|
||
auto from_iges = import_iges_from_string(iges);
|
||
EXPECT_FALSE(from_iges.empty());
|
||
EXPECT_TRUE(from_iges[0].is_valid());
|
||
|
||
// STEP 往返
|
||
std::string step = export_step({u});
|
||
auto from_step = import_step_from_string(step);
|
||
EXPECT_FALSE(from_step.empty());
|
||
EXPECT_TRUE(from_step[0].is_valid());
|
||
});
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// 持续运行模式
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(FuzzFormat, 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 body = rand_body();
|
||
ASSERT_TRUE(body.is_valid());
|
||
|
||
// IGES 往返
|
||
std::string iges = export_iges({body});
|
||
ASSERT_FALSE(iges.empty());
|
||
auto from_iges = import_iges_from_string(iges);
|
||
ASSERT_FALSE(from_iges.empty()) << "IGES import failed at iteration " << iteration;
|
||
ASSERT_TRUE(from_iges[0].is_valid());
|
||
|
||
// STEP 往返
|
||
std::string step = export_step({body});
|
||
ASSERT_FALSE(step.empty());
|
||
auto from_step = import_step_from_string(step);
|
||
ASSERT_FALSE(from_step.empty()) << "STEP import failed at iteration " << iteration;
|
||
ASSERT_TRUE(from_step[0].is_valid());
|
||
|
||
if (iteration % 1000 == 0) {
|
||
std::cout << "[FuzzFormat] iteration=" << iteration
|
||
<< " iges_len=" << iges.size()
|
||
<< " step_len=" << step.size()
|
||
<< std::endl;
|
||
}
|
||
}
|
||
}
|