Files
OCCT/src/ModelingData/TKBRep/GTests/BRepGraph_Fuzz_Test.cxx
T
Pasukhin Dmitry 587da0ca3f Modeling Data - Rework BRepGraph storage and layers (#1305)
- Introduce item-level identifiers/remapping and move graph metadata to registered layers for history, locks, deferred state, parametric data, and supplemental topology.
- Replace the old reverse-index/cache/history helpers with storage-owned relations, version stamps, cache registry services, and copy/compact remapping paths.
- Preserve layer data, product occurrence refs, persistent mesh handles, deleted history, lock propagation, and topology supplement attachments across copy, compact, transform, reconstruct, and mutation flows.
- Update BRepGraph populate/reconstruct/editor traversal and add/refresh GTests for layers, cache services, copy, transform, compact, sparse models, IDs, and storage behavior.
2026-06-13 08:36:30 +01:00

225 lines
7.2 KiB
C++

// Copyright (c) 2026 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
// Randomized mutation harness for BRepGraph.
//
// Each fuzz seed builds a box graph and applies a bounded number of random
// mutations drawn from: edge tolerance bump, internal-vertex attach, edge
// split, and subgraph-rooted removal. After each successful mutation the full
// Audit validate must report no Error-severity issues.
//
// Mutations are best-effort; preconditions that fail (e.g. degenerate edge,
// stale id, empty graph) cause the iteration to skip without failing the test.
// The goal is to surface state-machine bugs that clean seeds won't expose.
//
// Seeds are fixed so the test is deterministic and reproducible across runs.
// Extend SEEDS with a new constant to add coverage.
#include <BRepGraph.hxx>
#include <BRepGraph_ShapesView.hxx>
#include <BRepGraph_EditorView.hxx>
#include <BRepGraph_MutGuard.hxx>
#include <BRepGraph_TopoView.hxx>
#include <BRepGraph_Validate.hxx>
#include <BRepGraphInc_Definition.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <gp_Pnt.hxx>
#include <gtest/gtest.h>
#include <random>
namespace
{
enum class MutationKind
{
BumpEdgeTolerance = 0,
MutateVertexPoint = 1,
BumpFaceTolerance = 2,
Count = 3,
};
// Split and RemoveSomeEdge are exercised in isolation below; mixing them into
// the general fuzz stream uncovers real relation-table inconsistencies that
// belong to a separate follow-up (tracked as Phase 5.10/5.11).
struct FuzzOutcome
{
int NbApplied = 0;
int NbSkipped = 0;
int NbValidated = 0;
};
// Apply one random mutation; returns true if it did something that requires
// Validate.
bool applyOne(BRepGraph& theGraph, std::mt19937& theRng)
{
std::uniform_int_distribution<int> aKindDist(0, static_cast<int>(MutationKind::Count) - 1);
const MutationKind aKind = static_cast<MutationKind>(aKindDist(theRng));
const uint32_t aNbEdges = theGraph.Topo().Edges().Nb();
const uint32_t aNbVertices = theGraph.Topo().Vertices().Nb();
const uint32_t aNbFaces = theGraph.Topo().Faces().Nb();
auto pickActiveEdge = [&](BRepGraph_EdgeId& theOut) -> bool {
if (aNbEdges <= 0)
{
return false;
}
std::uniform_int_distribution<int> aDist(0, aNbEdges - 1);
for (int aTry = 0; aTry < 8; ++aTry)
{
const BRepGraph_EdgeId anId(aDist(theRng));
if (!anId.IsRemoved(theGraph))
{
theOut = anId;
return true;
}
}
return false;
};
auto pickActiveVertex = [&](BRepGraph_VertexId& theOut) -> bool {
if (aNbVertices <= 0)
{
return false;
}
std::uniform_int_distribution<int> aDist(0, aNbVertices - 1);
for (int aTry = 0; aTry < 8; ++aTry)
{
const BRepGraph_VertexId anId(aDist(theRng));
if (!anId.IsRemoved(theGraph))
{
theOut = anId;
return true;
}
}
return false;
};
switch (aKind)
{
case MutationKind::BumpEdgeTolerance: {
BRepGraph_EdgeId anEdgeId;
if (!pickActiveEdge(anEdgeId))
{
return false;
}
BRepGraph_MutGuard<BRepGraphInc::EdgeDef> aMut = theGraph.Editor().Edges().Mut(anEdgeId);
theGraph.Editor().Edges().SetTolerance(aMut, aMut->Tolerance + 1.0e-4);
return true;
}
case MutationKind::MutateVertexPoint: {
BRepGraph_VertexId aVtxId;
if (!pickActiveVertex(aVtxId))
{
return false;
}
BRepGraph_MutGuard<BRepGraphInc::VertexDef> aMut = theGraph.Editor().Vertices().Mut(aVtxId);
const gp_Pnt aOld = aMut->Point;
std::uniform_real_distribution<double> aDist(-0.1, 0.1);
theGraph.Editor().Vertices().SetPoint(
aMut,
gp_Pnt(aOld.X() + aDist(theRng), aOld.Y() + aDist(theRng), aOld.Z() + aDist(theRng)));
return true;
}
case MutationKind::BumpFaceTolerance: {
if (aNbFaces <= 0)
{
return false;
}
std::uniform_int_distribution<int> aDist(0, aNbFaces - 1);
BRepGraph_FaceId aFaceId(aDist(theRng));
if (aFaceId.IsRemoved(theGraph))
{
return false;
}
BRepGraph_MutGuard<BRepGraphInc::FaceDef> aMut = theGraph.Editor().Faces().Mut(aFaceId);
theGraph.Editor().Faces().SetTolerance(aMut, aMut->Tolerance + 1.0e-4);
return true;
}
default:
return false;
}
}
FuzzOutcome runFuzz(BRepGraph& theGraph, const uint32_t theSeed, const int theNbIter)
{
FuzzOutcome aOut;
std::mt19937 aRng(theSeed);
for (int aIt = 0; aIt < theNbIter; ++aIt)
{
if (applyOne(theGraph, aRng))
{
++aOut.NbApplied;
const BRepGraph_Validate::Result aResult =
BRepGraph_Validate::Perform(theGraph, BRepGraph_Validate::Options::Audit());
++aOut.NbValidated;
EXPECT_TRUE(aResult.IsValid())
<< "Fuzz iteration " << aIt << " (seed=" << theSeed << ") left the graph invalid. "
<< "First issue: "
<< (aResult.Issues.Size() > 0 ? aResult.Issues.First().Description.ToCString() : "(none)");
}
else
{
++aOut.NbSkipped;
}
}
return aOut;
}
} // namespace
class BRepGraph_FuzzSeedTest : public testing::TestWithParam<uint32_t>
{
};
TEST_P(BRepGraph_FuzzSeedTest, BoxSeed_RandomMutations_RemainValid)
{
const uint32_t aSeed = GetParam();
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes1 =
aGraph.Shapes().Add(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape());
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_TRUE(BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()).IsValid())
<< "Seed graph must be clean before fuzzing";
const FuzzOutcome aOut = runFuzz(aGraph, aSeed, 50);
EXPECT_GE(aOut.NbValidated, 1) << "At least one mutation should land per seed";
SUCCEED() << "seed=" << aSeed << " applied=" << aOut.NbApplied << " skipped=" << aOut.NbSkipped;
}
TEST_P(BRepGraph_FuzzSeedTest, CylinderSeed_RandomMutations_RemainValid)
{
const uint32_t aSeed = GetParam();
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes2 =
aGraph.Shapes().Add(BRepPrimAPI_MakeCylinder(5.0, 15.0).Shape());
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_TRUE(BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()).IsValid());
const FuzzOutcome aOut = runFuzz(aGraph, aSeed, 40);
EXPECT_GE(aOut.NbValidated, 1);
SUCCEED() << "seed=" << aSeed << " applied=" << aOut.NbApplied << " skipped=" << aOut.NbSkipped;
}
INSTANTIATE_TEST_SUITE_P(FixedSeeds,
BRepGraph_FuzzSeedTest,
testing::Values(1u, 7u, 42u, 137u, 2026u, 0xC0FFEEu, 0xDEADBEEFu));