From ab0508935fc7ba688b9447b51d06ffc071f36235 Mon Sep 17 00:00:00 2001 From: Silia TAIDER <silia.taider@cern.ch> Date: Tue, 16 Apr 2024 17:17:17 +0200 Subject: [PATCH] Add tests for the NTuple::GenericWriter and NTuple::Writer algorithms --- GaudiTestSuite/CMakeLists.txt | 2 + .../include/Gaudi/TestSuite/NTuple/MyStruct.h | 20 ++ GaudiTestSuite/src/IO/dict.h | 2 + GaudiTestSuite/src/IO/dict.xml | 2 + .../src/NTuple/NTupleWriterImpls.cpp | 26 ++ .../src/NTuple/NTupleWriterProducers.cpp | 65 +++++ .../pytest/NTuple/test_GenericNTupleWriter.py | 248 ++++++++++++++++++ .../tests/pytest/NTuple/test_NTupleWriter.py | 115 ++++++++ GaudiUtils/CMakeLists.txt | 13 + .../src/component/GenericNTupleWriter.cpp | 142 ++++++++++ 10 files changed, 635 insertions(+) create mode 100644 GaudiTestSuite/include/Gaudi/TestSuite/NTuple/MyStruct.h create mode 100644 GaudiTestSuite/src/NTuple/NTupleWriterImpls.cpp create mode 100644 GaudiTestSuite/src/NTuple/NTupleWriterProducers.cpp create mode 100644 GaudiTestSuite/tests/pytest/NTuple/test_GenericNTupleWriter.py create mode 100644 GaudiTestSuite/tests/pytest/NTuple/test_NTupleWriter.py diff --git a/GaudiTestSuite/CMakeLists.txt b/GaudiTestSuite/CMakeLists.txt index 92d40dfaf4..4a84a4fd6b 100644 --- a/GaudiTestSuite/CMakeLists.txt +++ b/GaudiTestSuite/CMakeLists.txt @@ -94,6 +94,8 @@ gaudi_add_module(GaudiTestSuiteComponents src/Timing/TimingAlg.cpp src/ToolHandles/Algorithms.cpp src/ToolHandles/FloatTool.cpp + src/NTuple/NTupleWriterProducers.cpp + src/NTuple/NTupleWriterImpls.cpp LINK GaudiKernel Gaudi::Functional GaudiUtilsLib diff --git a/GaudiTestSuite/include/Gaudi/TestSuite/NTuple/MyStruct.h b/GaudiTestSuite/include/Gaudi/TestSuite/NTuple/MyStruct.h new file mode 100644 index 0000000000..d74d1b98a0 --- /dev/null +++ b/GaudiTestSuite/include/Gaudi/TestSuite/NTuple/MyStruct.h @@ -0,0 +1,20 @@ +/*****************************************************************************\ +* (c) Copyright 2024 CERN for the benefit of the LHCb Collaboration * +* * +* This software is distributed under the terms of the GNU General Public * +* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". * +* * +* In applying this licence, CERN does not waive the privileges and immunities * +* granted to it by virtue of its status as an Intergovernmental Organization * +* or submit itself to any jurisdiction. * +\*****************************************************************************/ +#pragma once + +#include <string> + +namespace Gaudi::TestSuite::NTuple { + struct MyStruct { + int id{}; + std::string name; + }; +} // namespace Gaudi::TestSuite::NTuple diff --git a/GaudiTestSuite/src/IO/dict.h b/GaudiTestSuite/src/IO/dict.h index c97bf2bfa4..0028b94559 100644 --- a/GaudiTestSuite/src/IO/dict.h +++ b/GaudiTestSuite/src/IO/dict.h @@ -19,6 +19,8 @@ #include "GaudiTestSuite/MyTrack.h" #include "GaudiTestSuite/MyVertex.h" +#include <Gaudi/TestSuite/NTuple/MyStruct.h> + #include "GaudiExamples/Collision.h" #include "GaudiExamples/Counter.h" #include "GaudiExamples/Event.h" diff --git a/GaudiTestSuite/src/IO/dict.xml b/GaudiTestSuite/src/IO/dict.xml index 125bf61a02..7d183a1240 100644 --- a/GaudiTestSuite/src/IO/dict.xml +++ b/GaudiTestSuite/src/IO/dict.xml @@ -116,4 +116,6 @@ <field name="m_random" transient="true"/> </class> + <class name="Gaudi::TestSuite::NTuple::MyStruct"/> + </lcgdict> diff --git a/GaudiTestSuite/src/NTuple/NTupleWriterImpls.cpp b/GaudiTestSuite/src/NTuple/NTupleWriterImpls.cpp new file mode 100644 index 0000000000..376d97933e --- /dev/null +++ b/GaudiTestSuite/src/NTuple/NTupleWriterImpls.cpp @@ -0,0 +1,26 @@ +/***********************************************************************************\ +* (c) Copyright 2024 CERN for the benefit of the LHCb and ATLAS collaborations * +* * +* This software is distributed under the terms of the Apache version 2 licence, * +* copied verbatim in the file "LICENSE". * +* * +* In applying this licence, CERN does not waive the privileges and immunities * +* granted to it by virtue of its status as an Intergovernmental Organization * +* or submit itself to any jurisdiction. * +\***********************************************************************************/ +#include <Gaudi/NTuple/Writer.h> +#include <numeric> +#include <tuple> + +namespace Gaudi::TestSuite::NTuple { + struct NTupleWriter_V : Gaudi::NTuple::Writer<std::tuple<int, size_t, float>( const std::vector<int>& )> { + NTupleWriter_V( const std::string& name, ISvcLocator* svcLoc ) + : Writer( name, svcLoc, { KeyValue( "InputLocation", { "MyVector" } ) } ) {} + + std::tuple<int, size_t, float> transform( const std::vector<int>& vector ) const override { + return std::make_tuple( std::accumulate( vector.begin(), vector.end(), 0 ), vector.size(), std::rand() % 20 ); + } + }; + + DECLARE_COMPONENT( NTupleWriter_V ) +} // namespace Gaudi::TestSuite::NTuple diff --git a/GaudiTestSuite/src/NTuple/NTupleWriterProducers.cpp b/GaudiTestSuite/src/NTuple/NTupleWriterProducers.cpp new file mode 100644 index 0000000000..30bb0b9c0d --- /dev/null +++ b/GaudiTestSuite/src/NTuple/NTupleWriterProducers.cpp @@ -0,0 +1,65 @@ +/***********************************************************************************\ +* (c) Copyright 2024 CERN for the benefit of the LHCb and ATLAS collaborations * +* * +* This software is distributed under the terms of the Apache version 2 licence, * +* copied verbatim in the file "LICENSE". * +* * +* In applying this licence, CERN does not waive the privileges and immunities * +* granted to it by virtue of its status as an Intergovernmental Organization * +* or submit itself to any jurisdiction. * +\***********************************************************************************/ +#include <Gaudi/Algorithm.h> +#include <Gaudi/Functional/Producer.h> +#include <Gaudi/TestSuite/NTuple/MyStruct.h> +#include <string> +#include <vector> + +namespace Gaudi::TestSuite::NTuple { + struct IntVectorDataProducer final + : Gaudi::Functional::Producer<std::vector<int>(), Gaudi::Functional::Traits::BaseClass_t<Gaudi::Algorithm>> { + IntVectorDataProducer( const std::string& name, ISvcLocator* svcLoc ) + : Producer( name, svcLoc, KeyValue( "OutputLocation", "MyVector" ) ) {} + + std::vector<int> operator()() const override { return std::vector<int>{ 0, 1, 2, 3, 4 }; } + }; + + DECLARE_COMPONENT( IntVectorDataProducer ) + + struct FloatDataProducer final + : Gaudi::Functional::Producer<float(), Gaudi::Functional::Traits::BaseClass_t<Gaudi::Algorithm>> { + FloatDataProducer( const std::string& name, ISvcLocator* svcLoc ) + : Producer( name, svcLoc, KeyValue( "OutputLocation", "MyFloat" ) ) {} + + float operator()() const override { return 2.5; } + }; + + DECLARE_COMPONENT( FloatDataProducer ) + + struct StrDataProducer final + : Gaudi::Functional::Producer<std::string(), Gaudi::Functional::Traits::BaseClass_t<Gaudi::Algorithm>> { + StrDataProducer( const std::string& name, ISvcLocator* svcLoc ) + : Producer( name, svcLoc, KeyValue( "OutputLocation", "MyString" ) ) {} + + std::string operator()() const override { return m_stringValue; } + + private: + Gaudi::Property<std::string> m_stringValue{ this, "StringValue", "Default string", "Specify the string to write" }; + }; + + DECLARE_COMPONENT( StrDataProducer ) + + struct StructDataProducer final + : Gaudi::Functional::Producer<Gaudi::TestSuite::NTuple::MyStruct(), + Gaudi::Functional::Traits::BaseClass_t<Gaudi::Algorithm>> { + StructDataProducer( const std::string& name, ISvcLocator* svcLoc ) + : Producer( name, svcLoc, KeyValue( "OutputLocation", "MyStruct" ) ) {} + + Gaudi::TestSuite::NTuple::MyStruct operator()() const override { + Gaudi::TestSuite::NTuple::MyStruct myStruct = { 1, "myStruct" }; + return myStruct; + } + }; + + DECLARE_COMPONENT( StructDataProducer ) + +} // namespace Gaudi::TestSuite::NTuple diff --git a/GaudiTestSuite/tests/pytest/NTuple/test_GenericNTupleWriter.py b/GaudiTestSuite/tests/pytest/NTuple/test_GenericNTupleWriter.py new file mode 100644 index 0000000000..e866fd49dd --- /dev/null +++ b/GaudiTestSuite/tests/pytest/NTuple/test_GenericNTupleWriter.py @@ -0,0 +1,248 @@ +##################################################################################### +# (c) Copyright 2024 CERN for the benefit of the LHCb and ATLAS collaborations # +# # +# This software is distributed under the terms of the Apache version 2 licence, # +# copied verbatim in the file "LICENSE". # +# # +# In applying this licence, CERN does not waive the privileges and immunities # +# granted to it by virtue of its status as an Intergovernmental Organization # +# or submit itself to any jurisdiction. # +##################################################################################### +import os + +import pytest +import ROOT +from GaudiTests import run_gaudi + +# Constants for the output file name and expected values for verification +OUTPUT_FILE_NAME = "generic_ntuple_writer_tree.root" +EXPECTED_ENTRIES = 10 +EXPECTED_FLOAT_VALUE = 2.5 +EXPECTED_VECTOR_CONTENT = [0, 1, 2, 3, 4] +EXPECTED_STRING_VALUE = "hello world" +NUM_PRODUCERS = 1 + + +@pytest.fixture(scope="module", params=["st", "mt"]) +def setup_file_tree(tmp_path_factory, request): + """ + Fixture to set up the ROOT file and tree for testing with the given configuration. + Args: + tmp_path_factory: Factory for creating temporary directories provided by pytest. + request: Pytest request object, which contains a parameter for the configuration to use. + Yields: + Tuple of (ROOT file, ROOT TTree) for use in tests. + """ + os.chdir(tmp_path_factory.mktemp(request.param)) + if os.path.exists(OUTPUT_FILE_NAME): + os.remove(OUTPUT_FILE_NAME) + run_gaudi(f"{__file__}:config_{request.param}", check=True) + file = ROOT.TFile.Open(OUTPUT_FILE_NAME) + tree = file.Get("GenericWriterTree") + yield file, tree + file.Close() + + +def config_st(): + """ + Configuration function for the Gaudi application. Sets up components, services, and producers + """ + from Configurables import ApplicationMgr, Gaudi__NTuple__GenericWriter + from Configurables import Gaudi__RootCnvSvc as RootCnvSvc + from Configurables import ( + Gaudi__TestSuite__NTuple__FloatDataProducer, + Gaudi__TestSuite__NTuple__IntVectorDataProducer, + Gaudi__TestSuite__NTuple__StrDataProducer, + Gaudi__TestSuite__NTuple__StructDataProducer, + IncidentSvc, + MessageSvc, + ) + from Gaudi.Configuration import DEBUG, INFO + + # Output Levels + MessageSvc(OutputLevel=INFO) + IncidentSvc(OutputLevel=INFO) + RootCnvSvc(OutputLevel=INFO) + + # Create producers (float/std::vector/std::string/MyStruct) + producers = [ + Gaudi__TestSuite__NTuple__FloatDataProducer("FProducer", OutputLevel=DEBUG), + Gaudi__TestSuite__NTuple__IntVectorDataProducer("VProducer", OutputLevel=DEBUG), + Gaudi__TestSuite__NTuple__StrDataProducer( + "SProducer", OutputLevel=DEBUG, StringValue=EXPECTED_STRING_VALUE + ), + Gaudi__TestSuite__NTuple__StructDataProducer("STProducer", OutputLevel=DEBUG), + ] + + # Configure the NTupleWriter + NTupleWriter = Gaudi__NTuple__GenericWriter( + "NTupleWriter", OutputLevel=DEBUG, TreeFilename=OUTPUT_FILE_NAME + ) + NTupleWriter.ExtraInputs = [ + ("float", "MyFloat"), + ("std::vector<int>", "MyVector"), + ("std::string", "MyString"), + ("Gaudi::TestSuite::NTuple::MyStruct", "MyStruct"), + ] + # NTupleWriter.ExtraInputs = [ + # (alg.Output.Type, str(alg.Output)) + # for alg in producers + # ] + + # Application setup + ApplicationMgr( + TopAlg=producers + [NTupleWriter], + EvtMax=EXPECTED_ENTRIES, + EvtSel="NONE", + HistogramPersistency="NONE", + ) + + +def config_mt(): + """ + Configuration function for the Gaudi application. Sets up components, services, and producers + """ + from Configurables import ( + AlgResourcePool, + ApplicationMgr, + AvalancheSchedulerSvc, + Gaudi__NTuple__GenericWriter, + Gaudi__TestSuite__NTuple__FloatDataProducer, + Gaudi__TestSuite__NTuple__IntVectorDataProducer, + Gaudi__TestSuite__NTuple__StrDataProducer, + Gaudi__TestSuite__NTuple__StructDataProducer, + HiveSlimEventLoopMgr, + HiveWhiteBoard, + ) + from Gaudi.Configuration import DEBUG, WARNING + + # Configuration parameters for the multithreaded environment + evtslots = 4 + evtMax = EXPECTED_ENTRIES + threads = 4 + + # Whiteboard setup + whiteboard = HiveWhiteBoard("EventDataSvc", EventSlots=evtslots) + + # Event Loop Manager + slimeventloopmgr = HiveSlimEventLoopMgr( + SchedulerName="AvalancheSchedulerSvc", OutputLevel=WARNING + ) + + # Scheduler + AvalancheSchedulerSvc(ThreadPoolSize=threads, OutputLevel=WARNING) + + # Algorithm Resource Pool + AlgResourcePool(OutputLevel=DEBUG) + + # Create producers (float/std::vector/std::string/MyStruct) + producers = [ + Gaudi__TestSuite__NTuple__FloatDataProducer("FProducer", OutputLevel=DEBUG), + Gaudi__TestSuite__NTuple__IntVectorDataProducer("VProducer", OutputLevel=DEBUG), + Gaudi__TestSuite__NTuple__StrDataProducer( + "SProducer", OutputLevel=DEBUG, StringValue=EXPECTED_STRING_VALUE + ), + Gaudi__TestSuite__NTuple__StructDataProducer("STProducer", OutputLevel=DEBUG), + ] + + # NTupleWriter configuration + NTupleWriter = Gaudi__NTuple__GenericWriter( + "NTupleWriter", OutputLevel=DEBUG, TreeFilename=OUTPUT_FILE_NAME + ) + NTupleWriter.ExtraInputs = [ + ("float", "MyFloat"), + ("std::vector<int>", "MyVector"), + ("std::string", "MyString"), + ("Gaudi::TestSuite::NTuple::MyStruct", "MyStruct"), + ] + # NTupleWriter.ExtraInputs = [ + # (alg.Output.Type, str(alg.Output)) + # for alg in producers + # ] + + # Application setup + ApplicationMgr( + EvtMax=evtMax, + EvtSel="NONE", + ExtSvc=[whiteboard], + EventLoop=slimeventloopmgr, + TopAlg=producers + [NTupleWriter], + MessageSvcType="InertMessageSvc", + ) + + +def test_file_creation_and_tree_structure(setup_file_tree): + """ + Tests if the output ROOT file is created successfully and contains a tree + """ + file, tree = setup_file_tree + assert file, f"expected output file {OUTPUT_FILE_NAME} not found" + assert tree, "TTree 'NTupleWriterTree' not found in the file" + + +def test_branch_creation(setup_file_tree): + """ + Verifies that the expected branches are created in the ROOT file + """ + _, tree = setup_file_tree + assert tree.GetBranch("MyFloat") is not None, "Float branch was not created." + assert tree.GetBranch("MyVector") is not None, "Vector<int> branch was not created." + assert tree.GetBranch("MyString") is not None, "String branch was not created." + assert tree.GetBranch("MyStruct") is not None, "MyStruct branch was not created." + + +def test_float_branch_content(setup_file_tree): + """ + Tests the content of the float branch to ensure it matches the expected value + """ + _, tree = setup_file_tree + for entry in tree: + assert ( + entry.MyFloat == EXPECTED_FLOAT_VALUE + ), "Float branch does not contain the correct value." + + +def test_string_branch_content(setup_file_tree): + """ + Tests the content of the string branch to ensure it matches the expected value + """ + _, tree = setup_file_tree + for entry in tree: + assert ( + entry.MyString == EXPECTED_STRING_VALUE + ), "String branch does not contain the correct value." + + +def test_vector_branch_content(setup_file_tree): + """ + Tests the content of the std::vector<int> branch to ensure it matches the expected value + """ + _, tree = setup_file_tree + for entry in tree: + assert ( + list(entry.MyVector) == EXPECTED_VECTOR_CONTENT + ), "Vector<int> branch does not contain the correct values." + + +def test_multiple_entries(setup_file_tree): + """ + Basic check to ensure data for all expected events is present in the tree + """ + _, tree = setup_file_tree + assert ( + tree.GetEntries() == EXPECTED_ENTRIES + ), f"Expected {EXPECTED_ENTRIES} entries, found {tree.GetEntries()}." + + +def test_handling_missing_input(setup_file_tree): + """ + Checks how the algorithm handles a scenario where expected input data is missing + """ + _, tree = setup_file_tree + try: + missing_branch = tree.GetBranch("MissingData") + # Attempt to access a null pointer to trigger a ReferenceError + missing_branch.GetName() + assert False, "Branch 'MissingData' unexpectedly exists." + except ReferenceError: + assert True diff --git a/GaudiTestSuite/tests/pytest/NTuple/test_NTupleWriter.py b/GaudiTestSuite/tests/pytest/NTuple/test_NTupleWriter.py new file mode 100644 index 0000000000..cc9a842cc3 --- /dev/null +++ b/GaudiTestSuite/tests/pytest/NTuple/test_NTupleWriter.py @@ -0,0 +1,115 @@ +##################################################################################### +# (c) Copyright 2024 CERN for the benefit of the LHCb and ATLAS collaborations # +# # +# This software is distributed under the terms of the Apache version 2 licence, # +# copied verbatim in the file "LICENSE". # +# # +# In applying this licence, CERN does not waive the privileges and immunities # +# granted to it by virtue of its status as an Intergovernmental Organization # +# or submit itself to any jurisdiction. # +##################################################################################### +import os + +import pytest +import ROOT +from GaudiTests import run_gaudi + +# Constants for the output file name +OUTPUT_FILE_NAME = "ntuple_writer_tree.root" +EXPECTED_ENTRIES = 10 +EXPECTED_VECTOR_SUM = 10 +EXPECTED_VECTOR_SIZE = 5 + + +@pytest.fixture(scope="module") +def setup_file_tree(tmp_path_factory): + """ + PyTest fixture that prepares a ROOT file and tree for testing + Args: + tmp_path_factory: Factory for creating temporary directories provided by pytest + Yields: + Tuple of (ROOT file, ROOT TTree) for use in tests + """ + os.chdir(tmp_path_factory.getbasetemp()) + if os.path.exists(OUTPUT_FILE_NAME): + os.remove(OUTPUT_FILE_NAME) + run_gaudi(f"{__file__}:config", check=True) + file = ROOT.TFile.Open(OUTPUT_FILE_NAME) + tree = file.Get("WriterTree") + yield file, tree + file.Close() + + +def config(): + """ + Configuration function for the Gaudi application that sets up the NTupleWriter. + """ + from GaudiConfig2 import Configurables as C + + E = C.Gaudi + + algs = [ + E.TestSuite.NTuple.IntVectorDataProducer("IntVectorDataProducer"), + E.TestSuite.NTuple.NTupleWriter_V( + "NTupleWriter_V", + TreeFilename=OUTPUT_FILE_NAME, + BranchNames=["Branch1", "Branch2", "Branch3"], + ), + ] + + loopmgr = C.HiveSlimEventLoopMgr(SchedulerName="AvalancheSchedulerSvc") + whiteboard = C.HiveWhiteBoard("EventDataSvc", EventSlots=5) + svcs = [whiteboard, C.AlgResourcePool()] + return ( + [ + C.ApplicationMgr( + TopAlg=algs, + EvtMax=EXPECTED_ENTRIES, + EvtSel="NONE", + ExtSvc=svcs, + EventLoop=loopmgr.name, + ), + loopmgr, + ] + + algs + + svcs + ) + + +def test_branch_creation(setup_file_tree): + """ + Test to ensure that all expected branches are correctly created in the ROOT file. + """ + _, tree = setup_file_tree + assert tree.GetBranch("Branch1"), "Branch1 should exist in WriterTree." + assert tree.GetBranch("Branch2"), "Branch2 should exist in WriterTree." + assert tree.GetBranch("Branch3"), "Branch3 should exist in WriterTree." + + +def test_data_types(setup_file_tree): + """ + Verify the data within the branches to ensure they match expected transformations. + """ + _, tree = setup_file_tree + for entry in tree: + assert isinstance( + entry.Branch1, int + ), "Branch1 does not contain int values as expected." + assert isinstance( + entry.Branch2, int + ), "Branch2 does not contain int values as expected." + assert isinstance( + entry.Branch3, float + ), "Branch3 does not contain float values as expected." + + +def test_data_values(setup_file_tree): + """ """ + _, tree = setup_file_tree + for entry in tree: + assert ( + entry.Branch1 == EXPECTED_VECTOR_SUM + ), "Branch1 does not contain the correct value." + assert ( + entry.Branch2 == EXPECTED_VECTOR_SIZE + ), "Branch2 does not contain the correct value." diff --git a/GaudiUtils/CMakeLists.txt b/GaudiUtils/CMakeLists.txt index 85296f60d3..6860df35bb 100644 --- a/GaudiUtils/CMakeLists.txt +++ b/GaudiUtils/CMakeLists.txt @@ -96,4 +96,17 @@ if(BUILD_TESTING) PROPERTY DEPENDS ${package_name}.testXMLFileCatalogWrite) endif() + gaudi_add_executable(test_GenericNTupleWriter + SOURCES + src/component/GenericNTupleWriter.cpp + LINK + GaudiUtilsLib + Boost::headers + Boost::unit_test_framework + fmt::fmt + Gaudi::GaudiKernel + ROOT::Tree + TEST + ) + target_compile_definitions(test_GenericNTupleWriter PRIVATE UNIT_TESTS) endif() diff --git a/GaudiUtils/src/component/GenericNTupleWriter.cpp b/GaudiUtils/src/component/GenericNTupleWriter.cpp index 0fbb764ebf..c380de6823 100644 --- a/GaudiUtils/src/component/GenericNTupleWriter.cpp +++ b/GaudiUtils/src/component/GenericNTupleWriter.cpp @@ -157,3 +157,145 @@ namespace Gaudi::NTuple { DECLARE_COMPONENT( GenericWriter ) } // namespace Gaudi::NTuple + +#ifdef UNIT_TESTS + +# define BOOST_TEST_MODULE test_GenericNTupleWriter +# include <boost/test/unit_test.hpp> + +/** + * @class MockISvcLocator + * @brief Mock implementation of ISvcLocator interface for unit testing. + */ +class MockISvcLocator : public ISvcLocator { +public: + virtual StatusCode getService( const Gaudi::Utils::TypeNameString&, const InterfaceID&, IInterface*& ) override { + return StatusCode::SUCCESS; + } + + virtual StatusCode getService( const Gaudi::Utils::TypeNameString&, IService*&, const bool ) override { + return StatusCode::SUCCESS; + } + + virtual const std::list<IService*>& getServices() const override { + static std::list<IService*> dummyServices; + return dummyServices; + } + + virtual bool existsService( std::string_view ) const override { return false; } + + virtual SmartIF<IService>& service( const Gaudi::Utils::TypeNameString&, const bool ) override { + static SmartIF<IService> dummyService; + return dummyService; + } + + virtual unsigned long addRef() override { return 0; } + + virtual unsigned long release() override { return 0; } + + virtual StatusCode queryInterface( const InterfaceID&, void** ) override { return StatusCode::SUCCESS; } + + virtual std::vector<std::string> getInterfaceNames() const override { return {}; } + + virtual void* i_cast( const InterfaceID& ) const override { return nullptr; } + + virtual unsigned long refCount() const override { return 1; } +}; + +// Utility function tests +BOOST_AUTO_TEST_CASE( testGetTypeName ) { + // Test type name extraction from various formats + BOOST_CHECK_EQUAL( getTypeName( "MyClass" ), "MyClass" ); + BOOST_CHECK_EQUAL( getTypeName( "std::vector<double>" ), "std::vector<double>" ); + BOOST_CHECK_EQUAL( getTypeName( "UNKNOWN_CLASS:MyCustomClass" ), "MyCustomClass" ); +} + +BOOST_AUTO_TEST_CASE( testGetNameFromLoc ) { + // Test name extraction from location strings + BOOST_CHECK_EQUAL( getNameFromLoc( "/Event/MyAlg/MyData" ), "MyData" ); + BOOST_CHECK_EQUAL( getNameFromLoc( "MyAlg/MyData" ), "MyData" ); + BOOST_CHECK_EQUAL( getNameFromLoc( "MyData" ), "MyData" ); + BOOST_CHECK_EQUAL( getNameFromLoc( "" ), "" ); +} + +// Test instantiation of the GenericWriter +BOOST_AUTO_TEST_CASE( testInit ) { + MockISvcLocator mockLocator; + Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator ); + + // Verify we can instanciate a writer + BOOST_CHECK_EQUAL( writer.name(), "test_writer" ); +} + +// Test branch creation with empty dependencies +BOOST_AUTO_TEST_CASE( testCreateBranches_EmptyDeps ) { + MockISvcLocator mockLocator; + Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator ); + + // Expect an exception when no dependencies are provided + BOOST_CHECK_EXCEPTION( writer.initialize().ignore(), GaudiException, []( const GaudiException& e ) { + return e.message() == "No extra inputs locations specified. Please define extra inputs for the NTuple writer."; + } ); +} + +// Test branch creation with an invalid type +BOOST_AUTO_TEST_CASE( testCreateBranches_InvalidType ) { + MockISvcLocator mockLocator; + auto tree = std::make_unique<TTree>( "testTree", "test tree" ); + Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator ); + writer.setTree( tree.get() ); + + DataObjIDColl invalidDeps{ { "InvalidType", "loc" } }; + + // Expect an exception when an invalid type is provided + BOOST_CHECK_EXCEPTION( writer.createBranches( invalidDeps ), GaudiException, []( const GaudiException& e ) { + return e.message() == "Cannot create branch loc for unknown class: InvalidType. Provide a dictionary please."; + } ); +} + +// Test branch creation for fundamental types +BOOST_AUTO_TEST_CASE( testCreateBranches_BasicTypes ) { + MockISvcLocator mockLocator; + auto tree = std::make_unique<TTree>( "testTree", "test tree" ); + Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator ); + writer.setTree( tree.get() ); + + DataObjIDColl dependencies{ { "int", "loc1" }, { "double", "loc2" }, { "std::string", "loc3" } }; + std::unordered_set<std::string> expectedTypes{ "int", "double", "std::string" }; + writer.createBranches( dependencies ); + + // Verify that the branch wrappers' class names match the expected types + BOOST_CHECK_EQUAL( writer.getBranchWrappersSize(), expectedTypes.size() ); + BOOST_CHECK( expectedTypes == writer.getBranchesClassNames() ); +} + +// Test branch creation for ROOT-known non-fundamental types +BOOST_AUTO_TEST_CASE( testCreateBranches_ROOTKnownTypes ) { + MockISvcLocator mockLocator; + auto tree = std::make_unique<TTree>( "testTree", "test tree" ); + Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator ); + writer.setTree( tree.get() ); + + DataObjIDColl dependencies{ { "std::vector<double>", "vectorDoubleLoc" }, { "TH1D", "hist1DLoc" } }; + std::unordered_set<std::string> expectedTypes{ "std::vector<double>", "TH1D" }; + writer.createBranches( dependencies ); + + // Verify that the branch wrappers' class names match the expected types + BOOST_CHECK_EQUAL( writer.getBranchWrappersSize(), expectedTypes.size() ); + BOOST_CHECK( expectedTypes == writer.getBranchesClassNames() ); +} + +// Test for GaudiException when the file cannot be opened +BOOST_AUTO_TEST_CASE( testFileOpenException ) { + MockISvcLocator mockLocator; + Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator ); + DataObjIDColl dependencies{ { "float", "loc" } }; + + BOOST_CHECK( writer.setProperty( "ExtraInputs", dependencies ).isSuccess() ); + BOOST_CHECK( writer.setProperty( "TreeFilename", "/invalid/path/to/file.root" ).isSuccess() ); + BOOST_CHECK_EXCEPTION( writer.initialize().ignore(), GaudiException, []( const GaudiException& e ) { + return e.message() == "Failed to open file '/invalid/path/to/file.root'. Check file path and permissions."; + } ); +} + +#endif -- GitLab