From c0a537997c9f608106bfc88689a789ee349a49c3 Mon Sep 17 00:00:00 2001 From: wrtobin Date: Fri, 5 Dec 2025 16:16:43 -0800 Subject: [PATCH] Add DeprecatedGroup and ObsoleteGroup implementations with unit tests --- .../dataRepository/CMakeLists.txt | 2 + .../dataRepository/DeprecatedGroup.hpp | 173 ++++++ .../dataRepository/ObsoleteGroup.hpp | 495 ++++++++++++++++++ .../dataRepository/unitTests/CMakeLists.txt | 2 + .../unitTests/testDeprecatedGroup.cpp | 255 +++++++++ .../unitTests/testObsoleteGroup.cpp | 297 +++++++++++ src/coreComponents/schema/schema.xsd | 159 +++++- src/coreComponents/schema/schema.xsd.other | 50 +- 8 files changed, 1429 insertions(+), 4 deletions(-) create mode 100644 src/coreComponents/dataRepository/DeprecatedGroup.hpp create mode 100644 src/coreComponents/dataRepository/ObsoleteGroup.hpp create mode 100644 src/coreComponents/dataRepository/unitTests/testDeprecatedGroup.cpp create mode 100644 src/coreComponents/dataRepository/unitTests/testObsoleteGroup.cpp diff --git a/src/coreComponents/dataRepository/CMakeLists.txt b/src/coreComponents/dataRepository/CMakeLists.txt index 8d0fc0e09ce..c732b7e2b06 100644 --- a/src/coreComponents/dataRepository/CMakeLists.txt +++ b/src/coreComponents/dataRepository/CMakeLists.txt @@ -27,6 +27,7 @@ set( dataRepository_headers BufferOps_inline.hpp ConduitRestart.hpp DefaultValue.hpp + DeprecatedGroup.hpp ExecutableGroup.hpp Group.hpp HistoryDataSpec.hpp @@ -37,6 +38,7 @@ set( dataRepository_headers LogLevelsRegistry.hpp MappedVector.hpp ObjectCatalog.hpp + ObsoleteGroup.hpp ReferenceWrapper.hpp RestartFlags.hpp Utilities.hpp diff --git a/src/coreComponents/dataRepository/DeprecatedGroup.hpp b/src/coreComponents/dataRepository/DeprecatedGroup.hpp new file mode 100644 index 00000000000..a31afa9e494 --- /dev/null +++ b/src/coreComponents/dataRepository/DeprecatedGroup.hpp @@ -0,0 +1,173 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file DeprecatedGroup.hpp + * + * This file provides utilities for marking Group classes as deprecated while maintaining + * full functionality. + * + * @section deprecated_lifecycle Deprecated Stage in Group Lifecycle + * + * Classes in GEOS follow a four-stage lifecycle: + * + * 1. **Active**: Normal implementation with full functionality + * 2. **Deprecated**: Full functionality maintained, warnings logged on use (this file) + * 3. **Obsolete**: Type exists for catalog/schema compatibility, no functionality, errors on use + * 4. **Removed**: Class deleted entirely when version support window expires + * + * Use DeprecatedGroup to mark a class as deprecated while maintaining full functionality. + * Warnings are logged to inform users to migrate to the replacement. The class continues + * to function normally but alerts users to update their workflows. + * + * @see ObsoleteGroup.hpp for the next stage in the lifecycle + */ + +#ifndef GEOS_DATAREPOSITORY_DEPRECATEDGROUP_HPP_ +#define GEOS_DATAREPOSITORY_DEPRECATEDGROUP_HPP_ + +#include "Group.hpp" +#include "common/logger/Logger.hpp" +#include "common/format/Format.hpp" +#include "LvArray/src/system.hpp" +#include +#include + +namespace geos +{ +namespace dataRepository +{ + +/** + * @class DeprecatedGroup + * @brief A template wrapper that injects deprecation warnings into the inheritance hierarchy. + * + * This class template injects itself between a derived class and its base class T. + * When constructed, it logs a deprecation warning but maintains full functionality. + * + * @tparam T The base Group class to inherit from (e.g., Group, SolverBase, etc.) + * + * Usage pattern: + * @code + * // Original: + * class MyGroup : public Group { ... }; + * + * // After deprecation: + * class MyGroup : public DeprecatedGroup + * { + * public: + * static string deprecationMessage() { return "MyGroup is deprecated. Use NewGroup instead."; } + * // ... rest of implementation unchanged + * }; + * @endcode + */ +template< typename T > +class DeprecatedGroup : public T +{ +public: + + /** + * @brief Constructor + * @param name The name of this Group. + * @param parent The parent Group. + */ + explicit DeprecatedGroup( string const & name, Group * const parent ) + : T( name, parent ) + { + logDeprecationWarning(); + } + + /** + * @brief Constructor + * @param name The name of this Group. + * @param rootNode The root node of the data repository. + */ + explicit DeprecatedGroup( string const & name, conduit::Node & rootNode ) + : T( name, rootNode ) + { + logDeprecationWarning(); + } + + /** + * @brief Destructor + */ + virtual ~DeprecatedGroup() = default; + + /// Deleted default constructor + DeprecatedGroup() = delete; + + /// Deleted copy constructor + DeprecatedGroup( DeprecatedGroup const & ) = delete; + + /// Move constructor + DeprecatedGroup( DeprecatedGroup && ) = default; + + /// Deleted copy assignment + DeprecatedGroup & operator=( DeprecatedGroup const & ) = delete; + + /// Deleted move assignment + DeprecatedGroup & operator=( DeprecatedGroup && ) = delete; + +protected: + /** + * @brief Override initialization hook to keep a single, consistent touch point + * + * Deprecated groups currently do not need extra behavior at this stage, but + * overriding this hook keeps the lifecycle symmetric with ObsoleteGroup and + * provides a natural place for future deprecation-time checks if needed. + */ + virtual void initializePreSubGroups() override + { + T::initializePreSubGroups(); + } + +private: + /** + * @brief Log the deprecation warning message + */ + void logDeprecationWarning() + { + // Get the actual derived class type name from runtime type information + string const className = LvArray::system::demangleType< DeprecatedGroup< T > >( ); + string const message = getDeprecationMessage(); + + GEOS_LOG_RANK_0( GEOS_FMT( + "\n" + "********************************************************************************\n" + "* DEPRECATION WARNING\n" + "* Group: {}\n" + "* {}\n" + "********************************************************************************", + className, message ) ); + } + + /** + * @brief Get the deprecation message + * + * This uses runtime polymorphism to call the derived class's deprecationMessage() + * method if it exists, otherwise returns a default message. + * + * @return The deprecation message string + */ + virtual string getDeprecationMessage() const + { + return "This class is deprecated and may be removed in a future version."; + } +}; + +} // namespace dataRepository +} // namespace geos + +#endif // GEOS_DATAREPOSITORY_DEPRECATEDGROUP_HPP_ diff --git a/src/coreComponents/dataRepository/ObsoleteGroup.hpp b/src/coreComponents/dataRepository/ObsoleteGroup.hpp new file mode 100644 index 00000000000..2371fa12bc7 --- /dev/null +++ b/src/coreComponents/dataRepository/ObsoleteGroup.hpp @@ -0,0 +1,495 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file ObsoleteGroup.hpp + * + * This file provides utilities for marking Group classes as obsolete while maintaining + * catalog and schema compatibility for input parsing. + * + * @section obsolete_lifecycle Obsolete Stage in Group Lifecycle + * + * Use ObsoleteGroup when removing functionality but maintaining type for input compatibility: + * - Construction succeeds (for XML parsing and catalog registration) + * - All wrappers use NoopWrapper (zero memory footprint) + * - Initialization/execution throws clear error messages and is considered a programming error + * - No implementation beyond constructors required + * - Catalog entry preserved for schema validation + * + * This allows old input decks to parse without immediate failure while providing clear + * error messages if the obsolete functionality is actually invoked. Any attempt to + * "use" an obsolete group (beyond catalog/schema queries) is treated as a logic error + * in the calling code and is expected to terminate execution. + * + * @see DeprecatedGroup.hpp for the full lifecycle workflow documentation + */ + +#ifndef GEOS_DATAREPOSITORY_OBSOLETEGROUP_HPP_ +#define GEOS_DATAREPOSITORY_OBSOLETEGROUP_HPP_ + +#include "Group.hpp" +#include "Wrapper.hpp" +#include "common/logger/Logger.hpp" +#include "common/format/Format.hpp" +#include "codingUtilities/RTTypes.hpp" +#include "LvArray/src/system.hpp" +#include +#include +#include + +namespace geos +{ +namespace dataRepository +{ + +/** + * @class NoopWrapper + * @brief A wrapper shim that has type information but no memory footprint + * + * This class mimics the WrapperBase / Wrapper surface API but does not actually + * store any data. It is used by ObsoleteGroup to maintain type-ness and catalog + * compatibility without memory overhead. + * + * Semantics: + * - NoopWrapper is intended for schema/catalog use only (e.g. type discovery). + * - It must not be used for reading or writing simulation data. + * - All operations are implemented as no-ops and size/capacity/bytesAllocated are 0. + * + * Any code path that attempts to treat an obsolete group's wrappers as real + * storage (e.g. calling Wrapper::reference(), resize to hold state, etc.) + * is a programming error in the caller. Tests intentionally restrict themselves + * to WrapperBase-level queries when interacting with NoopWrapper. + * + * @tparam T The type that would normally be wrapped + */ +template< typename T > +class NoopWrapper final : public WrapperBase +{ +public: + /// Alias for the wrapped type + using TYPE = T; + + /** + * @brief Constructor + * @param name name of the object + * @param parent parent group which owns the Wrapper + */ + explicit NoopWrapper( string const & name, Group & parent ) + : WrapperBase( name, parent, rtTypes::getTypeName( typeid( T ) ) ) + { + // Mark as optional to avoid any required/default value checks + this->setInputFlag( InputFlags::OPTIONAL ); + } + + /// @brief Destructor + virtual ~NoopWrapper() noexcept override = default; + + /// @copydoc WrapperBase::size + virtual localIndex size() const override { return 0; } + + /// @copydoc WrapperBase::voidPointer + virtual void const * voidPointer() const override { return nullptr; } + + /// @copydoc WrapperBase::elementByteSize + virtual localIndex elementByteSize() const override { return sizeof( T ); } + + /// @copydoc WrapperBase::bytesAllocated + virtual size_t bytesAllocated() const override { return 0; } + + /// @copydoc WrapperBase::resize(int, localIndex const * const) + virtual void resize( int, localIndex const * const ) override {} + + /// @copydoc WrapperBase::reserve + virtual void reserve( localIndex const ) override {} + + /// @copydoc WrapperBase::capacity + virtual localIndex capacity() const override { return 0; } + + /// @copydoc WrapperBase::resize(localIndex) + virtual void resize( localIndex ) override {} + + /// @copydoc WrapperBase::copy + virtual void copy( localIndex const, localIndex const ) override {} + + /// @copydoc WrapperBase::erase + virtual void erase( std::set< localIndex > const & ) override {} + + /// @copydoc WrapperBase::move + virtual void move( LvArray::MemorySpace const, bool const ) const override {} + + /// @copydoc WrapperBase::getTypeRegex + virtual Regex const & getTypeRegex() const override + { + return rtTypes::getTypeRegex< T >(); + } + + /// @copydoc WrapperBase::getTypeId + virtual const std::type_info & getTypeId() const noexcept override + { + return typeid( T ); + } + + /// @copydoc WrapperBase::hasDefaultValue + virtual bool hasDefaultValue() const override { return false; } + + /// @copydoc WrapperBase::getDefaultValueString + virtual string getDefaultValueString() const override { return ""; } + + /// @copydoc WrapperBase::clone + virtual std::unique_ptr< WrapperBase > clone( string const & name, Group & parent ) override + { + return std::make_unique< NoopWrapper< T > >( name, parent ); + } + + /// @copydoc WrapperBase::copyWrapper + virtual void copyWrapper( WrapperBase const & ) override {} + + /// @copydoc WrapperBase::copyWrapperAttributes + virtual void copyWrapperAttributes( WrapperBase const & source ) override + { + WrapperBase::copyWrapperAttributes( source ); + } + + /// @copydoc WrapperBase::numArrayDims + virtual int numArrayDims() const override { return 0; } + + /// @copydoc WrapperBase::numArrayComp + virtual localIndex numArrayComp() const override { return 0; } + + /// @copydoc WrapperBase::setDimLabels + virtual WrapperBase & setDimLabels( integer const, Span< string const > const ) override + { + return *this; + } + + /// @copydoc WrapperBase::getDimLabels + virtual Span< string const > getDimLabels( integer const ) const override + { + return {}; + } + + /// @copydoc WrapperBase::getHistoryMetadata + virtual HistoryMetadata getHistoryMetadata( localIndex const = -1 ) const override final + { + return {}; + } + + /// @copydoc WrapperBase::isPackable + virtual bool isPackable( bool ) const override { return false; } + + /// @copydoc WrapperBase::unpack + virtual localIndex unpack( buffer_unit_type const * &, bool, bool, parallelDeviceEvents & ) override final + { + return 0; + } + + /// @copydoc WrapperBase::unpackByIndex + virtual localIndex unpackByIndex( buffer_unit_type const * &, arrayView1d< localIndex const > const &, + bool, bool, parallelDeviceEvents &, MPI_Op = MPI_REPLACE ) override + { + return 0; + } + + /// @copydoc WrapperBase::processInputFile + virtual bool processInputFile( xmlWrapper::xmlNode const &, xmlWrapper::xmlNodePos const & ) override + { + return false; + } + + /// @copydoc WrapperBase::addBlueprintField + virtual void addBlueprintField( conduit::Node &, string const &, string const &, + stdVector< string > const & = {} ) const override {} + + /// @copydoc WrapperBase::populateMCArray + virtual void populateMCArray( conduit::Node &, stdVector< string > const & = {} ) const override {} + + /// @copydoc WrapperBase::averageOverSecondDim + virtual std::unique_ptr< WrapperBase > averageOverSecondDim( string const & name, Group & parent ) const override + { + return std::make_unique< NoopWrapper< T > >( name, parent ); + } + + /// @copydoc WrapperBase::registerToWrite + virtual void registerToWrite() const override {} + + /// @copydoc WrapperBase::finishWriting + virtual void finishWriting() const override {} + + /// @copydoc WrapperBase::loadFromConduit + virtual bool loadFromConduit() override { return false; } + + /// @copydoc WrapperBase::copyData + virtual void copyData( WrapperBase const & ) override {} + +#if defined(USE_TOTALVIEW_OUTPUT) + /// @copydoc WrapperBase::totalviewTypeName + virtual string totalviewTypeName() const override + { + return LvArray::system::demangleType< T >(); + } +#endif + + /// @copydoc WrapperBase::createPythonObject + virtual PyObject * createPythonObject() override + { + return nullptr; + } + +private: + /// @copydoc WrapperBase::packPrivate + virtual localIndex packPrivate( buffer_unit_type * &, bool, bool, parallelDeviceEvents & ) const override + { + return 0; + } + + /// @copydoc WrapperBase::packSizePrivate + virtual localIndex packSizePrivate( bool, bool, parallelDeviceEvents & ) const override + { + return 0; + } + + /// @copydoc WrapperBase::packByIndexPrivate + virtual localIndex packByIndexPrivate( buffer_unit_type * &, arrayView1d< localIndex const > const &, + bool, bool, parallelDeviceEvents & ) const override + { + return 0; + } + + /// @copydoc WrapperBase::packByIndexSizePrivate + virtual localIndex packByIndexSizePrivate( arrayView1d< localIndex const > const &, bool, bool, + parallelDeviceEvents & ) const override + { + return 0; + } +}; + +/** + * @class ObsoleteGroup + * @brief A template wrapper that injects obsolete behavior into the inheritance hierarchy. + * + * This class template injects itself between a derived class and its base class T. + * The class type remains registered in the catalog for parsing compatibility, but + * any attempt to actually use the functionality produces a fatal error. + * + * Key behaviors: + * - Class remains in catalog for schema/parsing compatibility + * - Construction succeeds (for parsing), but initialization/execution fails with clear error + * - All wrapper registrations use NoopWrapper (no memory allocation) + * - No functional implementation is provided or required + * - Type information preserved for input validation + * + * Lifecycle workflow: + * 1. Active class: Normal Group implementation + * 2. Deprecated: Inherit from DeprecatedGroup, full functionality, logs warnings + * 3. Obsolete: Inherit from ObsoleteGroup, no functionality, errors on use + * 4. Removed: Delete class entirely when version support window expires + * + * @tparam T The base Group class to inherit from (e.g., Group, SolverBase, etc.) + * + * Usage pattern: + * @code + * // Step 1: Original active class + * class OldSolver : public SolverBase + * { + * public: + * OldSolver( string const & name, Group * parent ); + * virtual bool execute(...) override { ...implementation... } + * }; + * + * // Step 2: Mark as deprecated (transition period with warnings) + * class OldSolver : public DeprecatedGroup + * { + * public: + * static string deprecationMessage() { return "Use NewSolver instead"; } + * OldSolver( string const & name, Group * parent ); + * virtual bool execute(...) override { ...implementation still works... } + * }; + * + * // Step 3: Mark as obsolete (type exists, no functionality) + * class OldSolver : public ObsoleteGroup + * { + * public: + * static string obsoleteMessage() { return "Removed in v2.0. Use NewSolver instead."; } + * // Only constructors needed, no other implementation + * }; + * + * // Step 4: Eventually remove the class definition entirely + * @endcode + */ +template< typename T > +class ObsoleteGroup : public T +{ +public: + + /** + * @brief Constructor - allows object creation for catalog/parsing but logs warning + * @param name The name of this Group. + * @param parent The parent Group. + */ + explicit ObsoleteGroup( string const & name, Group * const parent ) + : T( name, parent ) + { + logObsoleteWarning(); + } + + /** + * @brief Constructor - allows object creation for catalog/parsing but logs warning + * @param name The name of this Group. + * @param rootNode The root node of the data repository. + */ + explicit ObsoleteGroup( string const & name, conduit::Node & rootNode ) + : T( name, rootNode ) + { + logObsoleteWarning(); + } + + /** + * @brief Destructor + */ + virtual ~ObsoleteGroup() = default; + + /// Deleted default constructor + ObsoleteGroup() = delete; + + /// Deleted copy constructor + ObsoleteGroup( ObsoleteGroup const & ) = delete; + + /// Move constructor + ObsoleteGroup( ObsoleteGroup && ) = default; + + /// Deleted copy assignment + ObsoleteGroup & operator=( ObsoleteGroup const & ) = delete; + + /// Deleted move assignment + ObsoleteGroup & operator=( ObsoleteGroup && ) = delete; + + /** + * @brief Register a no-op wrapper instead of a real one + * @tparam U The type to wrap + * @param name The name of the wrapper + * @return Reference to a NoopWrapper cast to Wrapper for API compatibility + * + * All wrapper registrations in obsolete groups use NoopWrapper to avoid memory + * allocation for unused functionality while maintaining API compatibility. + * + * IMPORTANT: + * - The returned reference is only valid to use through the WrapperBase API + * (e.g. size(), bytesAllocated(), getTypeId(), etc.). + * - Calling Wrapper-specific data accessors (such as reference(), setDefaultValue(), + * or methods that imply real storage) on this reference is undefined behavior and + * considered a programming error. + * - Obsolete groups are for catalog/schema compatibility only; callers must not + * attempt to read or write simulation data through their wrappers. + */ + template< typename U > + Wrapper< U > & registerWrapper( string const & name ) + { + // Create a NoopWrapper instead of a real Wrapper + auto noopWrapper = std::make_unique< NoopWrapper< U > >( name, *this ); + WrapperBase & base = T::registerWrapper( std::move( noopWrapper ) ); + // Cast for API compatibility - the wrapper will never be used functionally + return *reinterpret_cast< Wrapper< U > * >( &base ); + } + + /** + * @brief Register a no-op wrapper with explicit default value (value is ignored) + * @tparam U The type to wrap + * @param name The name of the wrapper + * @return Reference to a NoopWrapper cast to Wrapper for API compatibility + */ + template< typename U > + Wrapper< U > & registerWrapper( string const & name, U const & ) + { + return registerWrapper< U >( name ); + } + +protected: + /** + * @brief Override initialization to throw error - obsolete groups cannot be configured + * + * This hook is called on every group in the runtime tree after parsing. + * For obsolete groups, any attempt to reach this point is treated as a + * programming error (the group should never participate in configuration + * or execution), so we unconditionally trigger a hard failure. + */ + virtual void initializePreSubGroups() override + { + logObsoleteError(); + } + +private: + /** + * @brief Log a warning when obsolete group is constructed (for parsing) + */ + void logObsoleteWarning() const + { + // Get the actual derived class type name from runtime type information + string const className = LvArray::system::demangleType< ObsoleteGroup< T > >( ); + string const message = getObsoleteMessage(); + + GEOS_LOG_RANK_0( GEOS_FMT( + "\n" + "********************************************************************************\n" + "* OBSOLETE GROUP WARNING\n" + "* Group: {}\n" + "* {}\n" + "* This group has been removed and will cause an error if used.\n" + "* (Group object created for input parsing compatibility only)\n" + "********************************************************************************", + className, message ) ); + } + + /** + * @brief Log the obsolete error message and throw (called when actually trying to use) + */ + [[noreturn]] void logObsoleteError() const + { + // Get the actual derived class type name from runtime type information + string const className = LvArray::system::demangleType< ObsoleteGroup< T > >( ); + string const message = getObsoleteMessage(); + + GEOS_ERROR( GEOS_FMT( + "\n" + "********************************************************************************\n" + "* OBSOLETE GROUP ERROR\n" + "* Group: {}\n" + "* {}\n" + "* This group has been removed and cannot be used.\n" + "* Please update your input deck to use the replacement.\n" + "********************************************************************************", + className, message ) ); + + // This line should never be reached, but helps the compiler understand the noreturn attribute + std::terminate(); + } + + /** + * @brief Get the obsolete message + * + * This uses runtime polymorphism to call the derived class's obsoleteMessage() + * method if it exists, otherwise returns a default message. + * + * @return The obsolete message string + */ + virtual string getObsoleteMessage() const + { + return "This class is obsolete and has been removed."; + } +}; + +} // namespace dataRepository +} // namespace geos + +#endif // GEOS_DATAREPOSITORY_OBSOLETEGROUP_HPP_ diff --git a/src/coreComponents/dataRepository/unitTests/CMakeLists.txt b/src/coreComponents/dataRepository/unitTests/CMakeLists.txt index 9e7c32e37e6..5809674ff35 100644 --- a/src/coreComponents/dataRepository/unitTests/CMakeLists.txt +++ b/src/coreComponents/dataRepository/unitTests/CMakeLists.txt @@ -2,9 +2,11 @@ set( dataRepository_tests testBufferOps.cpp testDefaultValue.cpp + testDeprecatedGroup.cpp testDocumentationGeneration.cpp testErrorHandling.cpp testObjectCatalog.cpp + testObsoleteGroup.cpp testPacking.cpp testWrapperHelpers.cpp testWrapper.cpp diff --git a/src/coreComponents/dataRepository/unitTests/testDeprecatedGroup.cpp b/src/coreComponents/dataRepository/unitTests/testDeprecatedGroup.cpp new file mode 100644 index 00000000000..693edda2cb0 --- /dev/null +++ b/src/coreComponents/dataRepository/unitTests/testDeprecatedGroup.cpp @@ -0,0 +1,255 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +// Source includes +#include "dataRepository/DeprecatedGroup.hpp" +#include "dataRepository/Group.hpp" +#include "common/logger/Logger.hpp" +#include "dataRepository/DataContext.hpp" +#include "mainInterface/initialization.hpp" + +// TPL includes +#include +#include + +using namespace geos; +using namespace dataRepository; + +// Test fixture for DeprecatedGroup +class DeprecatedGroupTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Redirect cout to capture log output + m_oldCoutBuf = std::cout.rdbuf(); + std::cout.rdbuf( m_buffer.rdbuf() ); + } + + void TearDown() override + { + // Restore cout + std::cout.rdbuf( m_oldCoutBuf ); + } + + std::string getCapturedOutput() + { + return m_buffer.str(); + } + + void clearCapturedOutput() + { + m_buffer.str( "" ); + m_buffer.clear(); + } + +private: + std::stringstream m_buffer; + std::streambuf * m_oldCoutBuf; +}; + +// Test class that uses DeprecatedGroup with custom message +// DeprecatedGroup injects itself into the inheritance hierarchy +class TestDeprecatedGroupWithMessage : public DeprecatedGroup< Group > +{ +public: + TestDeprecatedGroupWithMessage( string const & name, Group * const parent ) + : DeprecatedGroup< Group >( name, parent ) + {} + + TestDeprecatedGroupWithMessage( string const & name, conduit::Node & rootNode ) + : DeprecatedGroup< Group >( name, rootNode ) + {} + + virtual string getDeprecationMessage() const override + { + return "TestDeprecatedGroupWithMessage is deprecated. Use NewTestGroup instead."; + } + + static string catalogName() { return "TestDeprecatedGroupWithMessage"; } +}; + +// Register the deprecated group in the catalog to test catalog compatibility +REGISTER_CATALOG_ENTRY( Group, TestDeprecatedGroupWithMessage, string const &, Group * const ); + +// Test class that uses DeprecatedGroup without custom message (uses default) +class TestDeprecatedGroupDefaultMessage : public DeprecatedGroup< Group > +{ +public: + TestDeprecatedGroupDefaultMessage( string const & name, Group * const parent ) + : DeprecatedGroup< Group >( name, parent ) + {} + + TestDeprecatedGroupDefaultMessage( string const & name, conduit::Node & rootNode ) + : DeprecatedGroup< Group >( name, rootNode ) + {} +}; + +// Test that DeprecatedGroup logs a warning on construction +TEST_F( DeprecatedGroupTest, DeprecatedGroupLogsWarning ) +{ + conduit::Node node; + clearCapturedOutput(); + + // Create a deprecated group - should log a warning + TestDeprecatedGroupWithMessage deprecatedGroup( "testDeprecated", node ); + + std::string output = getCapturedOutput(); + + // Check that output contains deprecation warning markers + EXPECT_NE( output.find( "DEPRECATION WARNING" ), std::string::npos ); + EXPECT_NE( output.find( "TestDeprecatedGroupWithMessage" ), std::string::npos ); + EXPECT_NE( output.find( "Use NewTestGroup instead" ), std::string::npos ); +} + +// Test that DeprecatedGroup with default message works +TEST_F( DeprecatedGroupTest, DeprecatedGroupDefaultMessage ) +{ + conduit::Node node; + clearCapturedOutput(); + + // Create a deprecated group with default message + TestDeprecatedGroupDefaultMessage deprecatedGroup( "testDeprecatedDefault", node ); + + std::string output = getCapturedOutput(); + + // Check that output contains deprecation warning with default message + EXPECT_NE( output.find( "DEPRECATION WARNING" ), std::string::npos ); + EXPECT_NE( output.find( "TestDeprecatedGroupDefaultMessage" ), std::string::npos ); + EXPECT_NE( output.find( "deprecated and may be removed in a future version" ), std::string::npos ); +} + +// Test that DeprecatedGroup maintains Group functionality +TEST_F( DeprecatedGroupTest, DeprecatedGroupFunctionality ) +{ + conduit::Node node; + clearCapturedOutput(); // Clear warning output + + TestDeprecatedGroupWithMessage deprecatedGroup( "testDeprecated", node ); + + // Test that basic Group operations still work + EXPECT_EQ( deprecatedGroup.getName(), "testDeprecated" ); + + // Test registering wrappers + deprecatedGroup.registerWrapper< int >( "testInt" ).setDefaultValue( 42 ); + EXPECT_TRUE( deprecatedGroup.hasWrapper( "testInt" ) ); + EXPECT_EQ( deprecatedGroup.getWrapper< int >( "testInt" ).reference(), 42 ); + + // Test registering subgroups + auto & subGroup = deprecatedGroup.registerGroup< Group >( "subGroup" ); + EXPECT_TRUE( deprecatedGroup.hasGroup( "subGroup" ) ); + EXPECT_EQ( &subGroup, deprecatedGroup.getGroupPointer( "subGroup" ) ); +} + +// Test that DeprecatedGroup can be used with parent constructor +TEST_F( DeprecatedGroupTest, DeprecatedGroupWithParentConstructor ) +{ + conduit::Node rootNode; + Group parentGroup( "parent", rootNode ); + + clearCapturedOutput(); + + // Create deprecated group as child of another group + TestDeprecatedGroupWithMessage deprecatedChild( "deprecatedChild", &parentGroup ); + + std::string output = getCapturedOutput(); + + // Should log warning + EXPECT_NE( output.find( "DEPRECATION WARNING" ), std::string::npos ); + + // Should be properly registered + EXPECT_EQ( deprecatedChild.getName(), "deprecatedChild" ); + EXPECT_EQ( &deprecatedChild.getParent(), &parentGroup ); +} + +// Test multiple instantiations of deprecated group +TEST_F( DeprecatedGroupTest, MultipleDeprecatedGroupInstances ) +{ + conduit::Node node1; + conduit::Node node2; + + clearCapturedOutput(); + + // Create multiple instances - each should log a warning + TestDeprecatedGroupWithMessage group1( "deprecated1", node1 ); + std::string output1 = getCapturedOutput(); + EXPECT_NE( output1.find( "DEPRECATION WARNING" ), std::string::npos ); + + clearCapturedOutput(); + + TestDeprecatedGroupWithMessage group2( "deprecated2", node2 ); + std::string output2 = getCapturedOutput(); + EXPECT_NE( output2.find( "DEPRECATION WARNING" ), std::string::npos ); +} + +// Test that DeprecatedGroup types are registered in the catalog and can be looked up +TEST_F( DeprecatedGroupTest, DeprecatedGroupCatalogRegistration ) +{ + // Check that the deprecated type is registered in the catalog + EXPECT_TRUE( Group::CatalogInterface::hasKeyName( "TestDeprecatedGroupWithMessage" ) ); + + // Verify we can get the list of catalog keys and our deprecated type is present + auto keys = Group::CatalogInterface::getKeys(); + bool found = false; + for( auto const & key : keys ) + { + if( key == "TestDeprecatedGroupWithMessage" ) + { + found = true; + break; + } + } + EXPECT_TRUE( found ); + + // Test that we can construct a deprecated group via the catalog factory + conduit::Node node; + Group parentGroup( "parent", node ); + + clearCapturedOutput(); + + // Use the catalog factory to create the deprecated group + DataFileContext const context = DataFileContext( "Test Deprecated Catalog", __FILE__, __LINE__ ); + std::unique_ptr< Group > deprecatedGroup = Group::CatalogInterface::factory( + "TestDeprecatedGroupWithMessage", + context, + "catalogCreatedDeprecated", + &parentGroup + ); + + // Verify the group was created + ASSERT_NE( deprecatedGroup, nullptr ); + EXPECT_EQ( deprecatedGroup->getName(), "catalogCreatedDeprecated" ); + + // Verify warning was logged during construction + std::string output = getCapturedOutput(); + EXPECT_NE( output.find( "DEPRECATION WARNING" ), std::string::npos ); + EXPECT_NE( output.find( "TestDeprecatedGroupWithMessage" ), std::string::npos ); + + clearCapturedOutput(); + + // Verify that the deprecated group still functions normally + deprecatedGroup->registerWrapper< int >( "testInt" ).setDefaultValue( 99 ); + EXPECT_TRUE( deprecatedGroup->hasWrapper( "testInt" ) ); + EXPECT_EQ( deprecatedGroup->getWrapper< int >( "testInt" ).reference(), 99 ); +} + +int main( int argc, char * * argv ) +{ + ::testing::InitGoogleTest( &argc, argv ); + geos::basicSetup( argc, argv, false ); + int const result = RUN_ALL_TESTS(); + geos::basicCleanup(); + return result; +} diff --git a/src/coreComponents/dataRepository/unitTests/testObsoleteGroup.cpp b/src/coreComponents/dataRepository/unitTests/testObsoleteGroup.cpp new file mode 100644 index 00000000000..2a92e594ecc --- /dev/null +++ b/src/coreComponents/dataRepository/unitTests/testObsoleteGroup.cpp @@ -0,0 +1,297 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +// Source includes +#include "dataRepository/ObsoleteGroup.hpp" +#include "dataRepository/Group.hpp" +#include "common/logger/Logger.hpp" +#include "dataRepository/DataContext.hpp" +#include "mainInterface/initialization.hpp" + +// TPL includes +#include +#include + +using namespace geos; +using namespace dataRepository; + +// Test fixture for ObsoleteGroup and NoopWrapper +class ObsoleteGroupTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Redirect cout to capture log output + m_oldCoutBuf = std::cout.rdbuf(); + std::cout.rdbuf( m_buffer.rdbuf() ); + } + + void TearDown() override + { + // Restore cout + std::cout.rdbuf( m_oldCoutBuf ); + } + + std::string getCapturedOutput() + { + return m_buffer.str(); + } + + void clearCapturedOutput() + { + m_buffer.str( "" ); + m_buffer.clear(); + } + +private: + std::stringstream m_buffer; + std::streambuf * m_oldCoutBuf; +}; + +// Test class that uses ObsoleteGroup with custom message +// ObsoleteGroup injects itself into the inheritance hierarchy +class TestObsoleteGroupWithMessage : public ObsoleteGroup< Group > +{ +public: + TestObsoleteGroupWithMessage( string const & name, Group * const parent ) + : ObsoleteGroup< Group >( name, parent ) + {} + + TestObsoleteGroupWithMessage( string const & name, conduit::Node & rootNode ) + : ObsoleteGroup< Group >( name, rootNode ) + {} + + virtual string getObsoleteMessage() const override + { + return "TestObsoleteGroupWithMessage has been removed. Use ReplacementGroup instead."; + } + + static string catalogName() { return "TestObsoleteGroupWithMessage"; } +}; + +// Register the obsolete group in the catalog to test catalog compatibility +REGISTER_CATALOG_ENTRY( Group, TestObsoleteGroupWithMessage, string const &, Group * const ); + +// Test that ObsoleteGroup allows construction but logs warning +TEST_F( ObsoleteGroupTest, ObsoleteGroupAllowsConstructionWithWarning ) +{ + conduit::Node node; + clearCapturedOutput(); + + // Creating an obsolete group should succeed (for parsing) but log warning + EXPECT_NO_THROW( + { + TestObsoleteGroupWithMessage obsoleteGroup( "testObsolete", node ); + + std::string output = getCapturedOutput(); + + // Check that warning was logged + EXPECT_NE( output.find( "OBSOLETE GROUP WARNING" ), std::string::npos ); + EXPECT_NE( output.find( "TestObsoleteGroupWithMessage" ), std::string::npos ); + EXPECT_NE( output.find( "Use ReplacementGroup instead" ), std::string::npos ); + EXPECT_NE( output.find( "input parsing compatibility only" ), std::string::npos ); + } + ); +} + +// Test that ObsoleteGroup throws error on initialization +TEST_F( ObsoleteGroupTest, ObsoleteGroupThrowsErrorOnInitialization ) +{ + conduit::Node node; + clearCapturedOutput(); + + TestObsoleteGroupWithMessage obsoleteGroup( "testObsolete", node ); + + // Clear the construction warning + clearCapturedOutput(); + + // Trying to initialize should throw an error (initialize() calls initializePreSubGroups()) + EXPECT_THROW( + { + obsoleteGroup.initialize(); + }, + std::exception + ); +} + +// Test that ObsoleteGroup uses NoopWrappers for registration +TEST_F( ObsoleteGroupTest, ObsoleteGroupUsesNoopWrappers ) +{ + conduit::Node node; + clearCapturedOutput(); + + TestObsoleteGroupWithMessage obsoleteGroup( "testObsolete", node ); + clearCapturedOutput(); // Clear construction warning + + // Register a wrapper - should be NoopWrapper + auto & wrapper = obsoleteGroup.registerWrapper< int >( "testInt" ); + + // The wrapper should exist but have no memory + EXPECT_TRUE( obsoleteGroup.hasWrapper( "testInt" ) ); + WrapperBase const & wrapperBase = obsoleteGroup.getWrapperBase( "testInt" ); + EXPECT_EQ( wrapperBase.size(), 0 ); + EXPECT_EQ( wrapperBase.bytesAllocated(), 0 ); +} + +// Test NoopWrapper size and memory methods +TEST_F( ObsoleteGroupTest, NoopWrapperHasNoMemoryFootprint ) +{ + conduit::Node node; + Group testGroup( "test", node ); + + NoopWrapper< int > noopWrapper( "noopInt", testGroup ); + + // Check that NoopWrapper reports no memory usage + EXPECT_EQ( noopWrapper.size(), 0 ); + EXPECT_EQ( noopWrapper.bytesAllocated(), 0 ); + EXPECT_EQ( noopWrapper.capacity(), 0 ); + EXPECT_EQ( noopWrapper.voidPointer(), nullptr ); + EXPECT_EQ( noopWrapper.elementByteSize(), sizeof( int ) ); +} + +// Test NoopWrapper operations are no-ops +TEST_F( ObsoleteGroupTest, NoopWrapperOperationsAreNoOps ) +{ + conduit::Node node; + Group testGroup( "test", node ); + + NoopWrapper< int > noopWrapper( "noopInt", testGroup ); + + // These should not throw and should do nothing + EXPECT_NO_THROW( noopWrapper.resize( 100 ) ); + EXPECT_NO_THROW( noopWrapper.reserve( 200 ) ); + EXPECT_NO_THROW( noopWrapper.copy( 0, 1 ) ); + EXPECT_NO_THROW( noopWrapper.erase( std::set< localIndex >{ 0, 1, 2 } ) ); + + // Size should still be 0 + EXPECT_EQ( noopWrapper.size(), 0 ); + EXPECT_EQ( noopWrapper.capacity(), 0 ); +} + +// Test NoopWrapper packing operations +TEST_F( ObsoleteGroupTest, NoopWrapperPackingReturnsZero ) +{ + conduit::Node node; + Group testGroup( "test", node ); + + NoopWrapper< int > noopWrapper( "noopInt", testGroup ); + + // Check that packing operations return 0 + EXPECT_FALSE( noopWrapper.isPackable( false ) ); + EXPECT_FALSE( noopWrapper.isPackable( true ) ); + + parallelDeviceEvents events; + buffer_unit_type * buffer = nullptr; + buffer_unit_type const * constBuffer = nullptr; + + EXPECT_EQ( noopWrapper.pack< false >( buffer, false, false, events ), 0 ); + EXPECT_EQ( noopWrapper.pack< true >( buffer, false, false, events ), 0 ); + EXPECT_EQ( noopWrapper.unpack( constBuffer, false, false, events ), 0 ); +} + +// Test NoopWrapper type information +TEST_F( ObsoleteGroupTest, NoopWrapperTypeInformation ) +{ + conduit::Node node; + Group testGroup( "test", node ); + + NoopWrapper< int > noopWrapper( "noopInt", testGroup ); + + // Check type information + EXPECT_EQ( noopWrapper.getTypeId(), typeid( int ) ); + EXPECT_EQ( noopWrapper.numArrayDims(), 0 ); + EXPECT_EQ( noopWrapper.numArrayComp(), 0 ); + EXPECT_FALSE( noopWrapper.hasDefaultValue() ); + EXPECT_EQ( noopWrapper.getDefaultValueString(), "" ); +} + +// Test NoopWrapper cloning +TEST_F( ObsoleteGroupTest, NoopWrapperCloning ) +{ + conduit::Node node; + Group testGroup( "test", node ); + + NoopWrapper< int > noopWrapper( "noopInt", testGroup ); + + // Clone the wrapper + auto cloned = noopWrapper.clone( "clonedNoop", testGroup ); + + EXPECT_NE( cloned.get(), nullptr ); + EXPECT_EQ( cloned->getName(), "clonedNoop" ); + EXPECT_EQ( cloned->size(), 0 ); +} + +// Test that ObsoleteGroup types are registered in the catalog and can be looked up +TEST_F( ObsoleteGroupTest, ObsoleteGroupCatalogRegistration ) +{ + // Check that the obsolete type is registered in the catalog + EXPECT_TRUE( Group::CatalogInterface::hasKeyName( "TestObsoleteGroupWithMessage" ) ); + + // Verify we can get the list of catalog keys and our obsolete type is present + auto keys = Group::CatalogInterface::getKeys(); + bool found = false; + for( auto const & key : keys ) + { + if( key == "TestObsoleteGroupWithMessage" ) + { + found = true; + break; + } + } + EXPECT_TRUE( found ); + + // Test that we can construct an obsolete group via the catalog factory + conduit::Node node; + Group parentGroup( "parent", node ); + + clearCapturedOutput(); + + // Use the catalog factory to create the obsolete group + DataFileContext const context = DataFileContext( "Test Obsolete Catalog", __FILE__, __LINE__ ); + std::unique_ptr< Group > obsoleteGroup = Group::CatalogInterface::factory( + "TestObsoleteGroupWithMessage", + context, + "catalogCreatedObsolete", + &parentGroup + ); + + // Verify the group was created + ASSERT_NE( obsoleteGroup, nullptr ); + EXPECT_EQ( obsoleteGroup->getName(), "catalogCreatedObsolete" ); + + // Verify warning was logged during construction + std::string output = getCapturedOutput(); + EXPECT_NE( output.find( "OBSOLETE GROUP WARNING" ), std::string::npos ); + EXPECT_NE( output.find( "TestObsoleteGroupWithMessage" ), std::string::npos ); + + clearCapturedOutput(); + + // Verify that trying to initialize still throws error (initialize() calls initializePreSubGroups()) + EXPECT_THROW( + { + obsoleteGroup->initialize(); + }, + std::exception + ); +} + +int main( int argc, char * * argv ) +{ + ::testing::InitGoogleTest( &argc, argv ); + basicSetup( argc, argv, false ); + int const result = RUN_ALL_TESTS(); + basicCleanup(); + return result; +} diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index 0d6554b6eef..6f03bb9ca76 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -463,6 +463,10 @@ + + + + @@ -491,6 +495,10 @@ + + + + @@ -569,6 +577,10 @@ + + + + @@ -581,6 +593,10 @@ + + + + @@ -2740,6 +2756,7 @@ Information output from lower logLevels is added with the desired log level + @@ -2747,6 +2764,7 @@ Information output from lower logLevels is added with the desired log level + @@ -3635,8 +3653,9 @@ Information output from lower logLevels is added with the desired log level +Frequency of pressure update is set in SinglePhase/CompositionalMultiphaseStatistics definition. +Setting cycleFrequency='1' will update the pressure every timestep, note that is a lagged property in constraint propertiesNote the event associated with the statists task must be entered before the solver event. +--> @@ -5028,6 +5047,60 @@ Local- Add jump stabilization on interior of macro elements--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5368,6 +5441,60 @@ Local- Add jump stabilization on interior of macro elements--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5912,9 +6039,11 @@ When set to `all` output both convergence & iteration information to a csv.--> + + @@ -6108,6 +6237,19 @@ Information output from lower logLevels is added with the desired log level + + + + + + + + + + + + + + + + + + + + diff --git a/src/coreComponents/schema/schema.xsd.other b/src/coreComponents/schema/schema.xsd.other index bea55da9276..35676a136fc 100644 --- a/src/coreComponents/schema/schema.xsd.other +++ b/src/coreComponents/schema/schema.xsd.other @@ -526,7 +526,7 @@ A field can represent a physical variable. (pressure, temperature, global compos - + @@ -595,6 +595,7 @@ A field can represent a physical variable. (pressure, temperature, global compos + @@ -602,6 +603,7 @@ A field can represent a physical variable. (pressure, temperature, global compos + @@ -1209,6 +1211,17 @@ A field can represent a physical variable. (pressure, temperature, global compos + + + + + + + + + + + @@ -1284,6 +1297,17 @@ A field can represent a physical variable. (pressure, temperature, global compos + + + + + + + + + + + @@ -1452,9 +1476,11 @@ A field can represent a physical variable. (pressure, temperature, global compos + + @@ -1484,9 +1510,11 @@ A field can represent a physical variable. (pressure, temperature, global compos + + @@ -1526,7 +1554,7 @@ A field can represent a physical variable. (pressure, temperature, global compos - + @@ -3559,6 +3587,12 @@ A field can represent a physical variable. (pressure, temperature, global compos + + + + + + @@ -3573,14 +3607,26 @@ A field can represent a physical variable. (pressure, temperature, global compos + + + + + + + + + + + +