diff --git a/ApplicationLibCode/GeoMech/GeoMechDataModel/RigFemPartResultsCollection.cpp b/ApplicationLibCode/GeoMech/GeoMechDataModel/RigFemPartResultsCollection.cpp index c7f548adec..e25a0a5c1b 100644 --- a/ApplicationLibCode/GeoMech/GeoMechDataModel/RigFemPartResultsCollection.cpp +++ b/ApplicationLibCode/GeoMech/GeoMechDataModel/RigFemPartResultsCollection.cpp @@ -215,7 +215,8 @@ RigFemPartResultsCollection::~RigFemPartResultsCollection() //-------------------------------------------------------------------------------------------------- void RigFemPartResultsCollection::setActiveFormationNames( const RigFormationNames* activeFormationNames ) { - m_activeFormationNamesData = activeFormationNames; + // Store a copy, as the owning RimFormationNames can be reloaded or deleted while this data is in use + m_activeFormationNamesData = activeFormationNames ? *activeFormationNames : RigFormationNames(); deleteResult( RigFemResultAddress( RIG_FORMATION_NAMES, "Active Formation Names", "" ) ); } @@ -238,7 +239,7 @@ std::vector RigFemPartResultsCollection::formationNames() const //-------------------------------------------------------------------------------------------------- const RigFormationNames* RigFemPartResultsCollection::activeFormationNames() const { - return m_activeFormationNamesData; + return m_activeFormationNamesData.isEmpty() ? nullptr : &m_activeFormationNamesData; } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/GeoMech/GeoMechDataModel/RigFemPartResultsCollection.h b/ApplicationLibCode/GeoMech/GeoMechDataModel/RigFemPartResultsCollection.h index 9f462a320a..452cefe716 100644 --- a/ApplicationLibCode/GeoMech/GeoMechDataModel/RigFemPartResultsCollection.h +++ b/ApplicationLibCode/GeoMech/GeoMechDataModel/RigFemPartResultsCollection.h @@ -20,6 +20,7 @@ #pragma once #include "RigFemResultAddress.h" +#include "RigFormationNames.h" #include "RimMudWeightWindowParameters.h" @@ -42,7 +43,6 @@ class RigFemPartResultsCollection; class RigFemPartResults; class RigStatisticsDataCache; class RigFemPartCollection; -class RigFormationNames; class RigFemPartResultCalculator; namespace caf @@ -205,7 +205,8 @@ private: cvf::ref m_readerInterface; cvf::ref m_elementPropertyReader; cvf::cref m_femParts; - const RigFormationNames* m_activeFormationNamesData = nullptr; + // Stored by value, as the owning RimFormationNames can be reloaded or deleted while this data is in use + RigFormationNames m_activeFormationNamesData; double m_cohesion; double m_frictionAngleRad; diff --git a/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp b/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp index c47f506d91..2cfa85e8e4 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp +++ b/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp @@ -3049,7 +3049,8 @@ void RigCaseCellResultsData::setHdf5Filename( const QString& hdf5SourSimFilename //-------------------------------------------------------------------------------------------------- void RigCaseCellResultsData::setActiveFormationNames( const RigFormationNames* activeFormationNames ) { - m_activeFormationNamesData = activeFormationNames; + // Store a copy, as the owning RimFormationNames can be reloaded or deleted while this data is in use + m_activeFormationNamesData = activeFormationNames ? *activeFormationNames : RigFormationNames(); if ( !activeFormationNames ) { @@ -3065,7 +3066,7 @@ void RigCaseCellResultsData::setActiveFormationNames( const RigFormationNames* a RiaResultNames::activeFormationNamesResultName() ), 0 ); - if ( !m_activeFormationNamesData ) + if ( m_activeFormationNamesData.isEmpty() ) { for ( size_t cIdx = 0; cIdx < totalGlobCellCount; ++cIdx ) { @@ -3123,7 +3124,7 @@ void RigCaseCellResultsData::setActiveFormationNames( const RigFormationNames* a //-------------------------------------------------------------------------------------------------- const RigFormationNames* RigCaseCellResultsData::activeFormationNames() const { - return m_activeFormationNamesData; + return m_activeFormationNamesData.isEmpty() ? nullptr : &m_activeFormationNamesData; } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.h b/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.h index 8f6a479930..2108510267 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.h +++ b/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.h @@ -23,6 +23,7 @@ #include "RiaDefines.h" #include "RiaPorosityModel.h" +#include "RigFormationNames.h" #include "RigStatisticsDataCache.h" #include @@ -38,7 +39,6 @@ class RigMainGrid; class RigEclipseResultInfo; class RigEclipseTimeStepInfo; class RigEclipseCaseData; -class RigFormationNames; class RigAllanDiagramData; class RigEclipseResultAddress; @@ -267,5 +267,6 @@ private: RigEclipseCaseData* m_ownerCaseData; RigActiveCellInfo* m_activeCellInfo; RiaDefines::PorosityModelType m_porosityModel; - const RigFormationNames* m_activeFormationNamesData = nullptr; + // Stored by value, as the owning RimFormationNames can be reloaded or deleted while this data is in use + RigFormationNames m_activeFormationNamesData; }; diff --git a/ApplicationLibCode/ReservoirDataModel/RigFormationNames.cpp b/ApplicationLibCode/ReservoirDataModel/RigFormationNames.cpp index 5c1375a6eb..89cc0def5e 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigFormationNames.cpp +++ b/ApplicationLibCode/ReservoirDataModel/RigFormationNames.cpp @@ -27,6 +27,14 @@ RigFormationNames::RigFormationNames() { } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RigFormationNames::isEmpty() const +{ + return m_formationNames.empty(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ReservoirDataModel/RigFormationNames.h b/ApplicationLibCode/ReservoirDataModel/RigFormationNames.h index ec1bd9fc44..54424660fd 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigFormationNames.h +++ b/ApplicationLibCode/ReservoirDataModel/RigFormationNames.h @@ -30,6 +30,7 @@ class RigFormationNames public: RigFormationNames(); + bool isEmpty() const; int formationIndexFromKLayerIdx( size_t Kidx ) const; QString formationNameFromKLayerIdx( size_t Kidx ) const; bool formationColorFromKLayerIdx( size_t Kidx, cvf::Color3f* formationColor ) const; diff --git a/ApplicationLibCode/UnitTests/CMakeLists.txt b/ApplicationLibCode/UnitTests/CMakeLists.txt index 4fbd643d14..3cc008a9ca 100644 --- a/ApplicationLibCode/UnitTests/CMakeLists.txt +++ b/ApplicationLibCode/UnitTests/CMakeLists.txt @@ -124,6 +124,7 @@ set(SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/RifGridCalculationIO-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifSummaryCalculationIO-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RimEmReader-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimFormationNames-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifPolygonReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifParquetReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifOsduWellPathReader-Test.cpp diff --git a/ApplicationLibCode/UnitTests/RimFormationNames-Test.cpp b/ApplicationLibCode/UnitTests/RimFormationNames-Test.cpp new file mode 100644 index 0000000000..0faa3e8f02 --- /dev/null +++ b/ApplicationLibCode/UnitTests/RimFormationNames-Test.cpp @@ -0,0 +1,119 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "gtest/gtest.h" + +#include "RiaTestDataDirectory.h" + +#include "Formations/RimFormationNames.h" +#include "RimEclipseResultCase.h" + +#include "RigEclipseCaseData.h" +#include "RigMainGrid.h" +#include "RigReservoirBuilder.h" + +#include +#include +#include + +#include +#include + +namespace +{ +struct MockCase +{ + std::unique_ptr resultCase; + cvf::ref eclipseCase; +}; + +//-------------------------------------------------------------------------------------------------- +/// Build a regular ni x nj x nk box grid in memory (no file, no view) +//-------------------------------------------------------------------------------------------------- +MockCase buildBoxGridCase( int ni, int nj, int nk ) +{ + RigReservoirBuilder builder; + builder.setIJKCount( cvf::Vec3st( ni, nj, nk ) ); + builder.setWorldCoordinates( cvf::Vec3d( 0.0, 0.0, 0.0 ), cvf::Vec3d( ni, nj, -nk ) ); + + MockCase mockCase; + mockCase.resultCase.reset( new RimEclipseResultCase ); + mockCase.eclipseCase = new RigEclipseCaseData( mockCase.resultCase.get() ); + + builder.createGridsAndCells( mockCase.eclipseCase.p() ); + mockCase.eclipseCase->mainGrid()->computeCachedData(); + + mockCase.resultCase->setReservoirData( mockCase.eclipseCase.p() ); + + return mockCase; +} + +std::unique_ptr readNorneFormationNames() +{ + QDir baseFolder( TEST_DATA_DIR ); + QString filePath = baseFolder.absoluteFilePath( "RifFormationNamesReader/Norne_ATW2013.lyr" ); + EXPECT_TRUE( QFile::exists( filePath ) ); + + auto formationNames = std::make_unique(); + formationNames->setFileName( filePath ); + + QString errorMessage; + formationNames->readFormationNamesFile( &errorMessage ); + EXPECT_TRUE( errorMessage.isEmpty() ); + + return formationNames; +} + +std::string joinedFormationNames( const RigEclipseCaseData* eclipseCase ) +{ + QStringList names; + for ( const QString& name : eclipseCase->formationNames() ) + { + names.push_back( name ); + } + + return names.join( "|" ).toStdString(); +} +} // namespace + +//-------------------------------------------------------------------------------------------------- +/// The case data must keep the formation names alive, as reloading or deleting the owning +/// RimFormationNames would otherwise leave a dangling pointer behind. +//-------------------------------------------------------------------------------------------------- +TEST( RimFormationNamesTest, CaseDataKeepsFormationNamesAliveAfterReloadAndDelete ) +{ + MockCase mockCase = buildBoxGridCase( 2, 2, 3 ); + auto formationNames = readNorneFormationNames(); + + mockCase.eclipseCase->setActiveFormationNames( formationNames->formationNamesData() ); + + const std::string namesBeforeReload = joinedFormationNames( mockCase.eclipseCase.p() ); + EXPECT_FALSE( namesBeforeReload.empty() ); + + // Reload replaces the data owned by RimFormationNames + QString errorMessage; + formationNames->readFormationNamesFile( &errorMessage ); + EXPECT_TRUE( errorMessage.isEmpty() ); + + EXPECT_EQ( namesBeforeReload, joinedFormationNames( mockCase.eclipseCase.p() ) ); + + // Deleting the owner must not invalidate the data referenced by the case data + formationNames.reset(); + + EXPECT_EQ( namesBeforeReload, joinedFormationNames( mockCase.eclipseCase.p() ) ); +}