From 5fdbaf7ea2489fe01a284c7bd43f6d27c621dbff Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Mon, 8 Jun 2026 17:04:46 +0200 Subject: [PATCH] #14057 RFT: Add unit tests for RifRftSegment branch and segment topology --- .../UnitTests/RifRftSegment-Test.cpp | 254 ++++++++++++++++-- 1 file changed, 233 insertions(+), 21 deletions(-) diff --git a/ApplicationLibCode/UnitTests/RifRftSegment-Test.cpp b/ApplicationLibCode/UnitTests/RifRftSegment-Test.cpp index f7b084dc20..5278a66dc0 100644 --- a/ApplicationLibCode/UnitTests/RifRftSegment-Test.cpp +++ b/ApplicationLibCode/UnitTests/RifRftSegment-Test.cpp @@ -4,58 +4,85 @@ #include "RiaRftDefines.h" +#include +#include #include +namespace +{ //-------------------------------------------------------------------------------------------------- -/// Verify that a device branch only claims the device segments that actually connect to its tubing -/// branch, and does not absorb device segments belonging to another tubing branch. +/// Build a topology with two tubing branches and their device segments. /// -/// Reproduces a topology where a lower-numbered tubing branch (index 1) is located at greater -/// measured depth than a higher-numbered tubing branch (index 2). The device segments are stored so -/// that the branch-2 device segments are immediately followed by the branch-1 device segments with -/// monotonically increasing measured depth. A measured-depth-only terminator would incorrectly -/// absorb the branch-1 device segments into branch index 2. +/// Tubing branch 1 (deep) : segment numbers 1-2, branch id 1 +/// Tubing branch 2 (shallow) : segment numbers 3-4, branch id 2 +/// Device segments for branch 2 : segment numbers 5-6, branch ids 11-12, outflow to segments 3-4 +/// Device segments for branch 1 : segment numbers 7-8, branch ids 13-14, outflow to segments 1-2 +/// +/// The device segments for branch 2 are stored immediately before the device segments for branch 1. //-------------------------------------------------------------------------------------------------- -TEST( RifRftSegmentTest, DeviceBranchDoesNotAbsorbSegmentsFromOtherBranch ) +RifRftSegment buildTwoBranchSegment() { // RifRftSegmentData( segnxt, brno, brnst, brnen, segNo ) std::vector topology; - // Tubing branch 1 (deep), segment numbers 1-2 topology.emplace_back( 0, 1, 1, 2, 1 ); topology.emplace_back( 1, 1, 1, 2, 2 ); - // Tubing branch 2 (shallow), segment numbers 3-4 topology.emplace_back( 0, 2, 3, 4, 3 ); topology.emplace_back( 3, 2, 3, 4, 4 ); - // Device segments feeding tubing branch 2 (shallow), segment numbers 5-6. - // Their outflow (segNext) points to the branch-2 tubing segments 3 and 4. topology.emplace_back( 3, 11, 5, 5, 5 ); topology.emplace_back( 4, 12, 6, 6, 6 ); - // Device segments feeding tubing branch 1 (deep), segment numbers 7-8. - // Their outflow (segNext) points to the branch-1 tubing segments 1 and 2. topology.emplace_back( 1, 13, 7, 7, 7 ); topology.emplace_back( 2, 14, 8, 8, 8 ); RifRftSegment segment; segment.setSegmentData( topology ); - // Measured depths (SEGLENST), indexed by topology position. Branch 1 (deep) is at greater - // measured depth than branch 2 (shallow), and the depths increase monotonically across the - // boundary between the branch-2 and branch-1 device segments. - std::vector seglenstValues = { 3700.0, 3800.0, 2700.0, 2800.0, 2700.0, 2800.0, 3700.0, 3800.0 }; + return segment; +} - // Classify the tubing branches, as done before device branches are identified. +//-------------------------------------------------------------------------------------------------- +/// Classify the two tubing branches, as done before device branches are identified. +//-------------------------------------------------------------------------------------------------- +void classifyTubingBranches( RifRftSegment& segment ) +{ segment.setBranchType( 1, RiaDefines::RftBranchType::RFT_TUBING ); segment.setOneBasedBranchIndex( 1, 1 ); segment.setBranchType( 2, RiaDefines::RftBranchType::RFT_TUBING ); segment.setOneBasedBranchIndex( 2, 2 ); +} + +//-------------------------------------------------------------------------------------------------- +/// Measured depths (SEGLENST) indexed by topology position. Branch 1 (deep) is at greater measured +/// depth than branch 2 (shallow), and the depths increase monotonically across the boundary between +/// the branch-2 and branch-1 device segments. +//-------------------------------------------------------------------------------------------------- +std::vector seglenstValues() +{ + return { 3700.0, 3800.0, 2700.0, 2800.0, 2700.0, 2800.0, 3700.0, 3800.0 }; +} +} // namespace + +//-------------------------------------------------------------------------------------------------- +/// Verify that a device branch only claims the device segments that actually connect to its tubing +/// branch, and does not absorb device segments belonging to another tubing branch. +/// +/// Reproduces a topology where a lower-numbered tubing branch (index 1) is located at greater +/// measured depth than a higher-numbered tubing branch (index 2). A measured-depth-only terminator +/// would incorrectly absorb the branch-1 device segments into branch index 2. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, DeviceBranchDoesNotAbsorbSegmentsFromOtherBranch ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + classifyTubingBranches( segment ); + + auto seglenst = seglenstValues(); // Build the device branch for tubing branch index 2, starting at the first device segment that // feeds branch 2 (segment number 5). - segment.createDeviceBranch( 5, 2, seglenstValues ); + segment.createDeviceBranch( 5, 2, seglenst ); // The device segments feeding branch 2 must be assigned to branch index 2. EXPECT_EQ( RiaDefines::RftBranchType::RFT_DEVICE, segment.branchType( 11 ) ); @@ -70,9 +97,194 @@ TEST( RifRftSegmentTest, DeviceBranchDoesNotAbsorbSegmentsFromOtherBranch ) EXPECT_EQ( -1, segment.oneBasedBranchIndexForBranchId( 14 ) ); // Building the device branch for tubing branch index 1 assigns the remaining device segments. - segment.createDeviceBranch( 7, 1, seglenstValues ); + segment.createDeviceBranch( 7, 1, seglenst ); EXPECT_EQ( RiaDefines::RftBranchType::RFT_DEVICE, segment.branchType( 13 ) ); EXPECT_EQ( RiaDefines::RftBranchType::RFT_DEVICE, segment.branchType( 14 ) ); EXPECT_EQ( 1, segment.oneBasedBranchIndexForBranchId( 13 ) ); EXPECT_EQ( 1, segment.oneBasedBranchIndexForBranchId( 14 ) ); } + +//-------------------------------------------------------------------------------------------------- +/// branchIds() returns the unique branch numbers in sorted order. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, BranchIdsAreUniqueAndSorted ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + + std::vector expected = { 1, 2, 11, 12, 13, 14 }; + EXPECT_EQ( expected, segment.branchIds() ); +} + +//-------------------------------------------------------------------------------------------------- +/// Segment lookup by segment number and by index, including lookups that do not match. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, SegmentLookup ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + + // Lookup by segment number + const auto* seg3 = segment.segmentData( 3 ); + ASSERT_NE( nullptr, seg3 ); + EXPECT_EQ( 3, seg3->segNo() ); + EXPECT_EQ( 2, seg3->segBrno() ); + + EXPECT_EQ( nullptr, segment.segmentData( 99 ) ); + + // Segment number to index + EXPECT_EQ( 2, segment.segmentIndexFromSegmentNumber( 3 ) ); + EXPECT_EQ( -1, segment.segmentIndexFromSegmentNumber( 99 ) ); + + // Lookup by index + const auto* firstSeg = segment.segmentDataByIndex( 0 ); + ASSERT_NE( nullptr, firstSeg ); + EXPECT_EQ( 1, firstSeg->segNo() ); + EXPECT_EQ( 1, firstSeg->segBrno() ); +} + +//-------------------------------------------------------------------------------------------------- +/// Unknown branches default to RFT_UNKNOWN type and branch index -1. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, UnknownBranchDefaults ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + + EXPECT_EQ( RiaDefines::RftBranchType::RFT_UNKNOWN, segment.branchType( 11 ) ); + EXPECT_EQ( -1, segment.oneBasedBranchIndexForBranchId( 11 ) ); + EXPECT_EQ( -1, segment.oneBasedBranchIndexForBranchId( 999 ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// tubingBranchIds() returns only the branches classified as tubing. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, TubingBranchIdsFiltersByType ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + + // No branch is classified yet + EXPECT_TRUE( segment.tubingBranchIds().empty() ); + + classifyTubingBranches( segment ); + + std::vector expected = { 1, 2 }; + EXPECT_EQ( expected, segment.tubingBranchIds() ); +} + +//-------------------------------------------------------------------------------------------------- +/// segmentIndicesForBranchNumber() returns the indices for one branch, or all when branchNumber<=0. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, SegmentIndicesForBranchNumber ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + + std::vector expectedBranch2 = { 2, 3 }; + EXPECT_EQ( expectedBranch2, segment.segmentIndicesForBranchNumber( 2 ) ); + + // branchNumber <= 0 returns all segment indices + EXPECT_EQ( 8u, segment.segmentIndicesForBranchNumber( 0 ).size() ); + EXPECT_EQ( 8u, segment.segmentIndicesForBranchNumber( -1 ).size() ); +} + +//-------------------------------------------------------------------------------------------------- +/// segmentIndicesForBranchIndex() filters on both the one-based branch index and the branch type. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, SegmentIndicesForBranchIndexFiltersByIndexAndType ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + classifyTubingBranches( segment ); + + std::vector expectedBranch2 = { 2, 3 }; + EXPECT_EQ( expectedBranch2, segment.segmentIndicesForBranchIndex( 2, RiaDefines::RftBranchType::RFT_TUBING ) ); + + std::vector expectedBranch1 = { 0, 1 }; + EXPECT_EQ( expectedBranch1, segment.segmentIndicesForBranchIndex( 1, RiaDefines::RftBranchType::RFT_TUBING ) ); + + // Matching index but wrong branch type yields nothing + EXPECT_TRUE( segment.segmentIndicesForBranchIndex( 2, RiaDefines::RftBranchType::RFT_DEVICE ).empty() ); + + // branchIndex <= 0 returns all segment indices + EXPECT_EQ( 8u, segment.segmentIndicesForBranchIndex( 0, RiaDefines::RftBranchType::RFT_TUBING ).size() ); +} + +//-------------------------------------------------------------------------------------------------- +/// segmentNumbersForBranchIndex() returns segment numbers for a given branch index and type. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, SegmentNumbersForBranchIndex ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + classifyTubingBranches( segment ); + + std::vector expected = { 3, 4 }; + EXPECT_EQ( expected, segment.segmentNumbersForBranchIndex( 2, RiaDefines::RftBranchType::RFT_TUBING ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// branchIdsAndOneBasedBranchIndices() filters by branch type, and returns all for RFT_UNKNOWN. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, BranchIdsAndOneBasedBranchIndicesFiltersByType ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + classifyTubingBranches( segment ); + + auto tubingMap = segment.branchIdsAndOneBasedBranchIndices( RiaDefines::RftBranchType::RFT_TUBING ); + std::map expectedTubing = { { 1, 1 }, { 2, 2 } }; + EXPECT_EQ( expectedTubing, tubingMap ); + + // No device branches are assigned yet + EXPECT_TRUE( segment.branchIdsAndOneBasedBranchIndices( RiaDefines::RftBranchType::RFT_DEVICE ).empty() ); + + // RFT_UNKNOWN returns all branches that have a one-based index assigned + auto allMap = segment.branchIdsAndOneBasedBranchIndices( RiaDefines::RftBranchType::RFT_UNKNOWN ); + EXPECT_EQ( expectedTubing, allMap ); +} + +//-------------------------------------------------------------------------------------------------- +/// After both device branches are built, they are reported with their tubing branch index. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, DeviceBranchesReportedAfterBuild ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + classifyTubingBranches( segment ); + + auto seglenst = seglenstValues(); + segment.createDeviceBranch( 5, 2, seglenst ); + segment.createDeviceBranch( 7, 1, seglenst ); + + auto deviceMap = segment.branchIdsAndOneBasedBranchIndices( RiaDefines::RftBranchType::RFT_DEVICE ); + std::map expectedDevice = { { 11, 2 }, { 12, 2 }, { 13, 1 }, { 14, 1 } }; + EXPECT_EQ( expectedDevice, deviceMap ); + + std::set expectedIndices = { 1, 2 }; + EXPECT_EQ( expectedIndices, segment.uniqueOneBasedBranchIndices( RiaDefines::RftBranchType::RFT_TUBING ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// createDeviceBranch() stops claiming segments when it reaches an already typed branch. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, DeviceBranchStopsAtAlreadyTypedSegment ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + classifyTubingBranches( segment ); + + // Pre-classify the first branch-2 device segment as a device segment of a different branch + segment.setBranchType( 11, RiaDefines::RftBranchType::RFT_DEVICE ); + segment.setOneBasedBranchIndex( 11, 1 ); + + auto seglenst = seglenstValues(); + segment.createDeviceBranch( 5, 2, seglenst ); + + // The already typed branch keeps its index and nothing further is claimed + EXPECT_EQ( 1, segment.oneBasedBranchIndexForBranchId( 11 ) ); + EXPECT_EQ( -1, segment.oneBasedBranchIndexForBranchId( 12 ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// nonContinuousDeviceSegmentIndices() returns nothing when there are fewer than two device segments. +//-------------------------------------------------------------------------------------------------- +TEST( RifRftSegmentTest, NonContinuousDeviceSegmentIndicesEmptyForFewSegments ) +{ + RifRftSegment segment = buildTwoBranchSegment(); + classifyTubingBranches( segment ); + + // Branch index 2 has no device segments before they are built + EXPECT_TRUE( segment.nonContinuousDeviceSegmentIndices( 2 ).empty() ); +}