From 14a4aa0f62ae235ea666f0afdd5b92387e5f8d98 Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Thu, 27 Aug 2026 08:33:59 +0200 Subject: [PATCH] #14621 Sector model export: Preserve include-only wrapper files Include files containing only INCLUDE statements, such as a wrapper collecting many lift curve (.ecl/.vfp) files, were dropped during sector model export, and the includes for all leaf files were written directly into the exported .DATA file. Update the custom-opm-common submodule so FileDeck recreates include-only files in the output directory and preserves the include hierarchy at any nesting depth. Add a regression test exporting a deck with a nested include-only wrapper and reloading the result. --- .../UnitTests/RifOpmFlowDeckFile-Test.cpp | 96 +++++++++++++++++++ ThirdParty/custom-opm-common/opm-common | 2 +- 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp b/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp index 43986208ec..8d62407d4a 100644 --- a/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp +++ b/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp @@ -656,6 +656,102 @@ TEST( RifOpmFlowDeckFileTest, SaveDeckLimitsLineWidthTo132Characters ) EXPECT_TRUE( reloadedDeckFile.loadDeck( savedFileName.toStdString() ).has_value() ); } +//-------------------------------------------------------------------------------------------------- +/// Issue #14621: include files containing only INCLUDE statements (e.g. a wrapper collecting many +/// lift curve files) were dropped when saving the deck, and all their child includes were written +/// directly into the main .DATA file. Verify that the include hierarchy is preserved. +//-------------------------------------------------------------------------------------------------- +TEST( RifOpmFlowDeckFileTest, SaveDeckPreservesIncludeOnlyWrapperFiles ) +{ + QTemporaryDir tempDir; + ASSERT_TRUE( tempDir.isValid() ); + ASSERT_TRUE( QDir().mkpath( tempDir.filePath( "include" ) ) ); + + auto writeTextFile = []( const QString& fileName, const QString& text ) + { + QFile file( fileName ); + ASSERT_TRUE( file.open( QIODevice::WriteOnly | QIODevice::Text ) ); + QTextStream out( &file ); + out << text; + }; + + auto vfpprodText = []( int tableNumber ) + { + QString text; + text += "VFPPROD\n"; + text += QString( " %1 2000.0 'LIQ' 'WCT' 'GOR' 'THP' ' ' 'METRIC' 'BHP' /\n" ).arg( tableNumber ); + text += " 100.0 /\n"; + text += " 10.0 /\n"; + text += " 0.0 /\n"; + text += " 0.0 /\n"; + text += " 0.0 /\n"; + text += " 1 1 1 1 200.0 /\n"; + return text; + }; + + // Main deck includes a wrapper file containing only INCLUDE statements. The wrapper includes + // one lift curve file directly, and a nested wrapper which includes a second lift curve file. + QString deckText; + deckText += "RUNSPEC\n"; + deckText += "DIMENS\n 2 2 2 /\n"; + deckText += "OIL\nWATER\nGAS\nMETRIC\n"; + deckText += "START\n 01 'JAN' 2000 /\n"; + deckText += "GRID\n"; + deckText += "DX\n8*100.0 /\n"; + deckText += "DY\n8*100.0 /\n"; + deckText += "DZ\n8*10.0 /\n"; + deckText += "TOPS\n4*2000.0 /\n"; + deckText += "PORO\n8*0.2 /\n"; + deckText += "PERMX\n8*100.0 /\n"; + deckText += "SCHEDULE\n"; + deckText += "INCLUDE\n 'include/lift_curves.inc' /\n"; + deckText += "END\n"; + writeTextFile( tempDir.filePath( "WRAPPER_INCLUDES.DATA" ), deckText ); + + QString wrapperText; + wrapperText += "INCLUDE\n 'include/vfp_curve_1.ecl' /\n"; + wrapperText += "INCLUDE\n 'include/more_curves.inc' /\n"; + writeTextFile( tempDir.filePath( "include/lift_curves.inc" ), wrapperText ); + + writeTextFile( tempDir.filePath( "include/more_curves.inc" ), "INCLUDE\n 'include/vfp_curve_2.ecl' /\n" ); + writeTextFile( tempDir.filePath( "include/vfp_curve_1.ecl" ), vfpprodText( 1 ) ); + writeTextFile( tempDir.filePath( "include/vfp_curve_2.ecl" ), vfpprodText( 2 ) ); + + RifOpmFlowDeckFile deckFile; + ASSERT_TRUE( deckFile.loadDeck( tempDir.filePath( "WRAPPER_INCLUDES.DATA" ).toStdString() ).has_value() ); + + QString outDir = tempDir.filePath( "out" ); + ASSERT_TRUE( QDir().mkpath( outDir ) ); + ASSERT_TRUE( deckFile.saveDeck( outDir.toStdString(), "WRAPPER_INCLUDES.DATA" ) ); + + auto readTextFile = []( const QString& fileName ) + { + QFile file( fileName ); + EXPECT_TRUE( file.open( QIODevice::ReadOnly | QIODevice::Text ) ) << "Missing file: " << fileName.toStdString(); + return QTextStream( &file ).readAll(); + }; + + // The main .DATA file must reference the wrapper only, not the individual lift curve files + QString mainText = readTextFile( outDir + "/WRAPPER_INCLUDES.DATA" ); + EXPECT_TRUE( mainText.contains( "include/lift_curves.inc" ) ); + EXPECT_FALSE( mainText.contains( "vfp_curve" ) ); + EXPECT_FALSE( mainText.contains( "more_curves" ) ); + + // The wrapper files must be recreated with their child includes + QString wrapperOutText = readTextFile( outDir + "/include/lift_curves.inc" ); + EXPECT_TRUE( wrapperOutText.contains( "include/vfp_curve_1.ecl" ) ); + EXPECT_TRUE( wrapperOutText.contains( "include/more_curves.inc" ) ); + + QString nestedWrapperOutText = readTextFile( outDir + "/include/more_curves.inc" ); + EXPECT_TRUE( nestedWrapperOutText.contains( "include/vfp_curve_2.ecl" ) ); + + // The exported model must still be parseable, with both lift curves present + RifOpmFlowDeckFile reloadedDeckFile; + ASSERT_TRUE( reloadedDeckFile.loadDeck( ( outDir + "/WRAPPER_INCLUDES.DATA" ).toStdString() ).has_value() ); + auto keywords = reloadedDeckFile.keywords( false ); + EXPECT_EQ( 2, std::count( keywords.begin(), keywords.end(), std::string( "VFPPROD" ) ) ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ThirdParty/custom-opm-common/opm-common b/ThirdParty/custom-opm-common/opm-common index fc00cc742d..13547103de 160000 --- a/ThirdParty/custom-opm-common/opm-common +++ b/ThirdParty/custom-opm-common/opm-common @@ -1 +1 @@ -Subproject commit fc00cc742da4a355a83efcfcce7ef84ad5f4fc2f +Subproject commit 13547103de76ee3e6e581f01b50c9af2bae820ab