VTK: Add unit tests for RifVtkReader and RifVtkImportUtil

Tests cover readPoints/readDisplacements/readConnectivity/readProperties
in RifVtkImportUtil, and file open, step names, FEM part loading,
displacement reading, and field names in RifVtkReader.
This commit is contained in:
Magne Sjaastad
2026-04-14 14:56:43 +02:00
parent b4d0c92d74
commit 9ff69a2089
6 changed files with 538 additions and 0 deletions
@@ -120,6 +120,8 @@ set(SOURCE_UNITTEST_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaResultName-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RigPolygonTools-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RifVtkSurfaceImporter-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RifVtkReader-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RifVtkImportUtil-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaKeyValueStore-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaEnsembleImportTools-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RimTools-Test.cpp
@@ -0,0 +1,282 @@
#include "RifVtkImportUtil.h"
#include "RiaTestDataDirectory.h"
#include <gtest/gtest.h>
#include "pugixml.hpp"
#include <cmath>
#include <filesystem>
#include <string>
// Helper: parse an XML string and return the <Piece> child of <UnstructuredGrid>
static pugi::xml_node parsePiece( pugi::xml_document& doc, const std::string& xml )
{
doc.load_string( xml.c_str() );
return doc.child( "UnstructuredGrid" ).child( "Piece" );
}
//--------------------------------------------------------------------------------------------------
// parsePvdDatasets
//--------------------------------------------------------------------------------------------------
TEST( RifVtkImportUtilTest, ParsePvdDatasets_ReturnsCorrectCount )
{
std::filesystem::path pvdPath = std::filesystem::path( TEST_DATA_DIR ) / "RifVtkReader" / "model.pvd";
auto datasets = RifVtkImportUtil::parsePvdDatasets( pvdPath );
ASSERT_EQ( datasets.size(), 2u );
EXPECT_DOUBLE_EQ( datasets[0].timestep, 1.0 );
EXPECT_DOUBLE_EQ( datasets[1].timestep, 2.0 );
}
TEST( RifVtkImportUtilTest, ParsePvdDatasets_PathsAreAbsolute )
{
std::filesystem::path pvdPath = std::filesystem::path( TEST_DATA_DIR ) / "RifVtkReader" / "model.pvd";
auto datasets = RifVtkImportUtil::parsePvdDatasets( pvdPath );
for ( const auto& ds : datasets )
{
EXPECT_TRUE( ds.filepath.is_absolute() );
EXPECT_TRUE( std::filesystem::exists( ds.filepath ) );
}
}
TEST( RifVtkImportUtilTest, ParsePvdDatasets_MissingFile )
{
auto datasets = RifVtkImportUtil::parsePvdDatasets( "nonexistent.pvd" );
EXPECT_TRUE( datasets.empty() );
}
//--------------------------------------------------------------------------------------------------
// readPoints
//--------------------------------------------------------------------------------------------------
TEST( RifVtkImportUtilTest, ReadPoints_ReturnsCoordinatesWithNegatedZ )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
<Points>
<DataArray type="Float32" Name="Coordinates" NumberOfComponents="3" format="ascii">
1 2 3 4 5 6
</DataArray>
</Points>
</Piece>
</UnstructuredGrid>)" );
auto points = RifVtkImportUtil::readPoints( piece );
ASSERT_EQ( points.size(), 2u );
EXPECT_DOUBLE_EQ( points[0].x(), 1.0 );
EXPECT_DOUBLE_EQ( points[0].y(), 2.0 );
EXPECT_DOUBLE_EQ( points[0].z(), -3.0 ); // z is negated
EXPECT_DOUBLE_EQ( points[1].x(), 4.0 );
EXPECT_DOUBLE_EQ( points[1].y(), 5.0 );
EXPECT_DOUBLE_EQ( points[1].z(), -6.0 ); // z is negated
}
TEST( RifVtkImportUtilTest, ReadPoints_MissingPointsSection )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
</Piece>
</UnstructuredGrid>)" );
auto points = RifVtkImportUtil::readPoints( piece );
EXPECT_TRUE( points.empty() );
}
TEST( RifVtkImportUtilTest, ReadPoints_WrongDataArrayName )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
<Points>
<DataArray type="Float32" Name="Points" NumberOfComponents="3" format="ascii">
1 2 3
</DataArray>
</Points>
</Piece>
</UnstructuredGrid>)" );
auto points = RifVtkImportUtil::readPoints( piece );
EXPECT_TRUE( points.empty() );
}
//--------------------------------------------------------------------------------------------------
// readDisplacements
//--------------------------------------------------------------------------------------------------
TEST( RifVtkImportUtilTest, ReadDisplacements_ReturnsVectorsWithNegatedZ )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
<PointData>
<DataArray type="Float32" Name="disp" NumberOfComponents="3" format="ascii">
0.1 0.2 0.3 -0.4 -0.5 -0.6
</DataArray>
</PointData>
</Piece>
</UnstructuredGrid>)" );
auto displacements = RifVtkImportUtil::readDisplacements( piece );
ASSERT_EQ( displacements.size(), 2u );
EXPECT_NEAR( displacements[0].x(), 0.1f, 1e-6f );
EXPECT_NEAR( displacements[0].y(), 0.2f, 1e-6f );
EXPECT_NEAR( displacements[0].z(), -0.3f, 1e-6f ); // z is negated
EXPECT_NEAR( displacements[1].x(), -0.4f, 1e-6f );
EXPECT_NEAR( displacements[1].y(), -0.5f, 1e-6f );
EXPECT_NEAR( displacements[1].z(), 0.6f, 1e-6f ); // z is negated
}
TEST( RifVtkImportUtilTest, ReadDisplacements_MissingPointDataSection )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
</Piece>
</UnstructuredGrid>)" );
auto displacements = RifVtkImportUtil::readDisplacements( piece );
EXPECT_TRUE( displacements.empty() );
}
TEST( RifVtkImportUtilTest, ReadDisplacements_WrongFieldName )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
<PointData>
<DataArray type="Float32" Name="velocity" NumberOfComponents="3" format="ascii">
1 2 3
</DataArray>
</PointData>
</Piece>
</UnstructuredGrid>)" );
auto displacements = RifVtkImportUtil::readDisplacements( piece );
EXPECT_TRUE( displacements.empty() );
}
//--------------------------------------------------------------------------------------------------
// readConnectivity
//--------------------------------------------------------------------------------------------------
TEST( RifVtkImportUtilTest, ReadConnectivity_ReturnsIndices )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
<Cells>
<DataArray type="Int32" Name="connectivity" format="ascii">
0 1 4 3 6 7 10 9
1 2 5 4 7 8 11 10
</DataArray>
<DataArray type="Int32" Name="offsets" format="ascii">8 16</DataArray>
<DataArray type="UInt8" Name="types" format="ascii">12 12</DataArray>
</Cells>
</Piece>
</UnstructuredGrid>)" );
auto connectivity = RifVtkImportUtil::readConnectivity( piece );
ASSERT_EQ( connectivity.size(), 16u );
EXPECT_EQ( connectivity[0], 0u );
EXPECT_EQ( connectivity[1], 1u );
EXPECT_EQ( connectivity[7], 9u );
EXPECT_EQ( connectivity[8], 1u );
EXPECT_EQ( connectivity[15], 10u );
}
TEST( RifVtkImportUtilTest, ReadConnectivity_MissingCellsSection )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
</Piece>
</UnstructuredGrid>)" );
auto connectivity = RifVtkImportUtil::readConnectivity( piece );
EXPECT_TRUE( connectivity.empty() );
}
//--------------------------------------------------------------------------------------------------
// readProperties
//--------------------------------------------------------------------------------------------------
TEST( RifVtkImportUtilTest, ReadProperties_ReturnsNamedScalars )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
<CellData>
<DataArray type="Float32" Name="Stress" format="ascii">100.0 200.0</DataArray>
<DataArray type="Float32" Name="Pressure" format="ascii">1.5 2.5</DataArray>
</CellData>
</Piece>
</UnstructuredGrid>)" );
auto properties = RifVtkImportUtil::readProperties( piece );
ASSERT_EQ( properties.size(), 2u );
ASSERT_TRUE( properties.count( "Stress" ) > 0 );
ASSERT_TRUE( properties.count( "Pressure" ) > 0 );
ASSERT_EQ( properties["Stress"].size(), 2u );
EXPECT_FLOAT_EQ( properties["Stress"][0], 100.0f );
EXPECT_FLOAT_EQ( properties["Stress"][1], 200.0f );
ASSERT_EQ( properties["Pressure"].size(), 2u );
EXPECT_FLOAT_EQ( properties["Pressure"][0], 1.5f );
EXPECT_FLOAT_EQ( properties["Pressure"][1], 2.5f );
}
TEST( RifVtkImportUtilTest, ReadProperties_HandlesNaN )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
<CellData>
<DataArray type="Float32" Name="Perm" format="ascii">1.0 nan 3.0</DataArray>
</CellData>
</Piece>
</UnstructuredGrid>)" );
auto properties = RifVtkImportUtil::readProperties( piece );
ASSERT_TRUE( properties.count( "Perm" ) > 0 );
ASSERT_EQ( properties["Perm"].size(), 3u );
EXPECT_FLOAT_EQ( properties["Perm"][0], 1.0f );
EXPECT_TRUE( std::isnan( properties["Perm"][1] ) );
EXPECT_FLOAT_EQ( properties["Perm"][2], 3.0f );
}
TEST( RifVtkImportUtilTest, ReadProperties_MissingCellDataSection )
{
pugi::xml_document doc;
auto piece = parsePiece( doc, R"(
<UnstructuredGrid>
<Piece>
</Piece>
</UnstructuredGrid>)" );
auto properties = RifVtkImportUtil::readProperties( piece );
EXPECT_TRUE( properties.empty() );
}
@@ -0,0 +1,167 @@
#include "RifVtkReader.h"
#include "RiaTestDataDirectory.h"
#include "RigFemPartCollection.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <string>
#include <vector>
static std::filesystem::path testDataDir()
{
return std::filesystem::path( TEST_DATA_DIR ) / "RifVtkReader";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RifVtkReaderTest, OpenValidFile )
{
RifVtkReader reader;
EXPECT_FALSE( reader.isOpen() );
std::string errorMessage;
bool opened = reader.openFile( ( testDataDir() / "model.pvd" ).string(), &errorMessage );
EXPECT_TRUE( opened );
EXPECT_TRUE( reader.isOpen() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RifVtkReaderTest, OpenInvalidFile )
{
RifVtkReader reader;
std::string errorMessage;
bool opened = reader.openFile( ( testDataDir() / "nonexistent.pvd" ).string(), &errorMessage );
EXPECT_FALSE( opened );
EXPECT_FALSE( reader.isOpen() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RifVtkReaderTest, StepNames )
{
RifVtkReader reader;
reader.openFile( ( testDataDir() / "model.pvd" ).string(), nullptr );
cvf::ref<RigFemPartCollection> femParts = new RigFemPartCollection();
reader.readFemParts( femParts.p() );
auto stepNames = reader.allStepNames();
ASSERT_EQ( stepNames.size(), 2u );
EXPECT_EQ( reader.frameCount( 0 ), 1 );
EXPECT_EQ( reader.frameCount( 1 ), 1 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RifVtkReaderTest, ReadFemParts )
{
RifVtkReader reader;
reader.openFile( ( testDataDir() / "model.pvd" ).string(), nullptr );
cvf::ref<RigFemPartCollection> femParts = new RigFemPartCollection();
bool result = reader.readFemParts( femParts.p() );
ASSERT_TRUE( result );
ASSERT_EQ( femParts->partCount(), 1 );
const RigFemPart* part = femParts->part( 0 );
EXPECT_EQ( part->nodeCount(), 12 );
EXPECT_EQ( part->elementCount(), 2 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RifVtkReaderTest, ReadDisplacements )
{
RifVtkReader reader;
reader.openFile( ( testDataDir() / "model.pvd" ).string(), nullptr );
cvf::ref<RigFemPartCollection> femParts = new RigFemPartCollection();
reader.readFemParts( femParts.p() );
// Step 0: all displacements are zero
{
std::vector<cvf::Vec3f> displacements;
reader.readDisplacements( 0, 0, 0, &displacements );
ASSERT_EQ( displacements.size(), 12u );
for ( const auto& d : displacements )
{
EXPECT_FLOAT_EQ( d.x(), 0.0f );
EXPECT_FLOAT_EQ( d.y(), 0.0f );
EXPECT_FLOAT_EQ( d.z(), 0.0f );
}
}
// Step 1: displacements are (0.1, 0.2, 0.3) in file, z is negated on read
{
std::vector<cvf::Vec3f> displacements;
reader.readDisplacements( 0, 1, 0, &displacements );
ASSERT_EQ( displacements.size(), 12u );
for ( const auto& d : displacements )
{
EXPECT_NEAR( d.x(), 0.1f, 1e-5f );
EXPECT_NEAR( d.y(), 0.2f, 1e-5f );
EXPECT_NEAR( d.z(), -0.3f, 1e-5f );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RifVtkReaderTest, ScalarFieldNames )
{
RifVtkReader reader;
reader.openFile( ( testDataDir() / "model.pvd" ).string(), nullptr );
cvf::ref<RigFemPartCollection> femParts = new RigFemPartCollection();
reader.readFemParts( femParts.p() );
auto nodeFields = reader.scalarNodeFieldAndComponentNames();
ASSERT_TRUE( nodeFields.count( "U" ) > 0 );
auto& uComponents = nodeFields["U"];
EXPECT_EQ( uComponents.size(), 3u );
EXPECT_EQ( uComponents[0], "U1" );
EXPECT_EQ( uComponents[1], "U2" );
EXPECT_EQ( uComponents[2], "U3" );
auto elementFields = reader.scalarElementFieldAndComponentNames();
EXPECT_TRUE( elementFields.count( "Stress" ) > 0 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RifVtkReaderTest, ReadNodeFieldDisplacement )
{
RifVtkReader reader;
reader.openFile( ( testDataDir() / "model.pvd" ).string(), nullptr );
cvf::ref<RigFemPartCollection> femParts = new RigFemPartCollection();
reader.readFemParts( femParts.p() );
std::vector<float> u1( 12 ), u2( 12 ), u3( 12 );
std::vector<float>* components[3] = { &u1, &u2, &u3 };
std::vector<std::vector<float>*> resultValues( components, components + 3 );
reader.readNodeField( "U", 0, 1, 0, &resultValues );
ASSERT_EQ( u1.size(), 12u );
for ( size_t i = 0; i < 12; i++ )
{
EXPECT_NEAR( u1[i], 0.1f, 1e-5f );
EXPECT_NEAR( u2[i], 0.2f, 1e-5f );
EXPECT_NEAR( u3[i], -0.3f, 1e-5f );
}
}
@@ -0,0 +1,40 @@
<?xml version="1.0"?>
<VTKFile type="UnstructuredGrid" version="0.1" byte_order="LittleEndian">
<UnstructuredGrid>
<Piece NumberOfCells="2" NumberOfPoints="12">
<PointData>
<DataArray type="Float32" Name="disp" NumberOfComponents="3" format="ascii">
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
</DataArray>
</PointData>
<CellData>
<DataArray type="Float32" Name="Stress" NumberOfComponents="1" format="ascii">
100.0 200.0
</DataArray>
</CellData>
<Points>
<DataArray type="Float32" Name="Coordinates" NumberOfComponents="3" format="ascii">
0 0 0 1 0 0 2 0 0
0 1 0 1 1 0 2 1 0
0 0 1 1 0 1 2 0 1
0 1 1 1 1 1 2 1 1
</DataArray>
</Points>
<Cells>
<DataArray type="Int32" Name="connectivity" NumberOfComponents="1" format="ascii">
0 1 4 3 6 7 10 9
1 2 5 4 7 8 11 10
</DataArray>
<DataArray type="Int32" Name="offsets" NumberOfComponents="1" format="ascii">
8 16
</DataArray>
<DataArray type="UInt8" Name="types" NumberOfComponents="1" format="ascii">
12 12
</DataArray>
</Cells>
</Piece>
</UnstructuredGrid>
</VTKFile>
@@ -0,0 +1,40 @@
<?xml version="1.0"?>
<VTKFile type="UnstructuredGrid" version="0.1" byte_order="LittleEndian">
<UnstructuredGrid>
<Piece NumberOfCells="2" NumberOfPoints="12">
<PointData>
<DataArray type="Float32" Name="disp" NumberOfComponents="3" format="ascii">
0.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.3
0.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.3
0.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.3
0.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.3
</DataArray>
</PointData>
<CellData>
<DataArray type="Float32" Name="Stress" NumberOfComponents="1" format="ascii">
150.0 250.0
</DataArray>
</CellData>
<Points>
<DataArray type="Float32" Name="Coordinates" NumberOfComponents="3" format="ascii">
0 0 0 1 0 0 2 0 0
0 1 0 1 1 0 2 1 0
0 0 1 1 0 1 2 0 1
0 1 1 1 1 1 2 1 1
</DataArray>
</Points>
<Cells>
<DataArray type="Int32" Name="connectivity" NumberOfComponents="1" format="ascii">
0 1 4 3 6 7 10 9
1 2 5 4 7 8 11 10
</DataArray>
<DataArray type="Int32" Name="offsets" NumberOfComponents="1" format="ascii">
8 16
</DataArray>
<DataArray type="UInt8" Name="types" NumberOfComponents="1" format="ascii">
12 12
</DataArray>
</Cells>
</Piece>
</UnstructuredGrid>
</VTKFile>
@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<VTKFile type="Collection" version="0.1" byte_order="LittleEndian">
<Collection>
<DataSet timestep="1.0" file="model-00000.vtu"/>
<DataSet timestep="2.0" file="model-00001.vtu"/>
</Collection>
</VTKFile>