Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 2 additions & 14 deletions roottest/root/ntuple/makeproject/rntuple/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,14 @@ ROOTTEST_ADD_TEST(makeproject_rntuple

# Read back the class instance thanks to the shared library generated by MakeProject.
# Make sure it corresponds to the data stored in the original custom class
if(MSVC)
# Windows is more picky with the library name, provide full path to the test lib
set(RNTUPLESTLTESTLIB ${CMAKE_CURRENT_BINARY_DIR}/librntuplestltest/librntuplestltest.lib)
else()
set(RNTUPLESTLTESTLIB rntuplestltest)
endif()
ROOTTEST_GENERATE_EXECUTABLE(
read_rntuple
read_rntuple.cxx
LIBRARIES ${ROOT_LIBRARIES} GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main ${RNTUPLESTLTESTLIB} ROOTNTuple
LIBRARIES GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main Core
FIXTURES_REQUIRED makeproject_rntuple_called
FIXTURES_SETUP read_rntuple_executable
)
target_include_directories(read_rntuple PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_directories(read_rntuple PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/librntuplestltest)

# Need to also explicitly set the LD_LIBRARY_PATH (at least on MacOS), so that
# the generated library with the custom class can be loaded by the read test.
ROOTTEST_ADD_TEST(read_rntuple
EXEC ./read_rntuple
FIXTURES_REQUIRED read_rntuple_executable
# PATH is used on Windows to find libraries for loading
ENVIRONMENT ${ld_library_path}=${CMAKE_CURRENT_BINARY_DIR}/librntuplestltest)
FIXTURES_REQUIRED read_rntuple_executable)
55 changes: 33 additions & 22 deletions roottest/root/ntuple/makeproject/rntuple/read_rntuple.cxx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#include <ROOT/RNTupleModel.hxx>
#include <ROOT/RNTupleReader.hxx>
#include "librntuplestltest/MySTLEvent.h"
#include <TInterpreter.h>
#include <TSystem.h>

#include "gtest/gtest.h"

#include <bitset>
#include <unordered_map>
#include <unordered_set>

void check_bitset(const std::bitset<16> &a, const std::bitset<16> &b)
{
EXPECT_EQ(a, b);
Expand Down Expand Up @@ -32,25 +36,32 @@ void check_unordered_multimap(const std::unordered_multimap<std::string, std::ve
EXPECT_EQ(a, b);
}

template <typename T>
T interpreter_get(const char *expr)
{
T *ptr = reinterpret_cast<T *>(gInterpreter->Calc(expr));
EXPECT_NE(ptr, nullptr);
return *ptr;
}

TEST(RNTupleMakeProject, ReadBackRNTuple)
{
auto ntuple = ROOT::RNTupleReader::Open("events", "ntuple_makeproject_stl_example_rntuple.root");
ASSERT_EQ(gSystem->Load("librntuplestltest/librntuplestltest"), 0);

#ifdef _MSC_VER
// The Microsoft linker uses an optimization such that a library is not going
// to be linked against even if it was explicitly requested in case that its
// objects are not explicitly used in the program. In this particular test,
// the class MySTLEvent is used as the template argument for GetView, but
// this apparently does not qualify as usage for the linker. In fact, without
// the following line which just instantiates a dummy MySTLEvent object, this
// executable would not be linked against the shared library created by the
// TFile::MakeProject call.
[[maybe_unused]] MySTLEvent dummy;
#endif
// Everything involving MySTLEvent lives in the interpreter
gInterpreter->ProcessLine(R"(
auto ntuple = ROOT::RNTupleReader::Open("events", "ntuple_makeproject_stl_example_rntuple.root");

auto viewEvent = ntuple->GetView<MySTLEvent>("test");
auto viewEvent = ntuple->GetView<MySTLEvent>("test");
const auto &event = viewEvent(0);
)");

const auto &event = viewEvent(0);
// Get values out as plain STL types
auto event_foo = interpreter_get<std::bitset<16>>("&event.foo");
Copy link
Copy Markdown
Member

@pcanal pcanal Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cumbersome and thus once has to wonder whether we would be 'better' serve by switching from an executable to a script ... I suspect that the down side is to have to 'replace' the EXPECT_EQ macro (or maybe gtest works 'fine' in a script?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would also be a good solution! There are many options and we'll discuss next week what to do

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would also be a good solution! There are many options and we'll discuss next week what to do

auto event_bar = interpreter_get<std::vector<int>>("&event.bar");
auto event_spam = interpreter_get<std::unordered_set<double>>("&event.spam");
auto event_eggs = interpreter_get<std::unordered_map<int, std::string>>("&event.eggs");
auto event_strange = interpreter_get<std::unordered_multimap<std::string, std::vector<float>>>("&event.strange");

std::bitset<16> foo = 0xfa2;
std::vector<int> bar = {1, 2};
Expand All @@ -59,11 +70,11 @@ TEST(RNTupleMakeProject, ReadBackRNTuple)
std::unordered_multimap<std::string, std::vector<float>> strange = {
{"one", {1, 2, 3}}, {"one", {4, 5, 6}}, {"two", {7, 8, 9}}};

check_bitset(event.foo, foo);
check_vector(event.bar, bar);
check_unordered_set(event.spam, spam);
check_unordered_map(event.eggs, eggs);
check_unordered_multimap(event.strange, strange);
check_bitset(event_foo, foo);
check_vector(event_bar, bar);
check_unordered_set(event_spam, spam);
check_unordered_map(event_eggs, eggs);
check_unordered_multimap(event_strange, strange);
}

int main(int argc, char **argv)
Expand Down
16 changes: 2 additions & 14 deletions roottest/root/ntuple/makeproject/ttree/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,14 @@ ROOTTEST_ADD_TEST(makeproject_ttree

# Read back the class instance thanks to the shared library generated by MakeProject.
# Make sure it corresponds to the data stored in the original custom class
if(MSVC)
# Windows is more picky with the library name, provide full path to the test lib
set(TTREESTLTESTLIB ${CMAKE_CURRENT_BINARY_DIR}/libttreestltest/libttreestltest.lib)
else()
set(TTREESTLTESTLIB ttreestltest)
endif()
ROOTTEST_GENERATE_EXECUTABLE(
read_ttree
read_ttree.cxx
LIBRARIES ${ROOT_LIBRARIES} GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main ${TTREESTLTESTLIB}
LIBRARIES GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main Core RIO Tree
FIXTURES_REQUIRED makeproject_ttree_called
FIXTURES_SETUP makeproject_read_ttree_executable
)
target_include_directories(read_ttree PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_directories(read_ttree PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libttreestltest)

# Need to also explicitly set the LD_LIBRARY_PATH (at least on MacOS), so that
# the generated library with the custom class can be loaded by the read test.
ROOTTEST_ADD_TEST(read_ttree
EXEC ./read_ttree
FIXTURES_REQUIRED makeproject_read_ttree_executable
# PATH is used on Windows to find libraries for loading
ENVIRONMENT ${ld_library_path}=${CMAKE_CURRENT_BINARY_DIR}/libttreestltest)
FIXTURES_REQUIRED makeproject_read_ttree_executable)
49 changes: 40 additions & 9 deletions roottest/root/ntuple/makeproject/ttree/read_ttree.cxx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
#include <TFile.h>
#include <TInterpreter.h>
#include <TString.h>
#include <TSystem.h>
#include <TTree.h>

#include "libttreestltest/MySTLEvent.h"
#include "gtest/gtest.h"

#include <cstdint>
#include <bitset>
#include <unordered_map>
#include <unordered_set>

void check_bitset(const std::bitset<16> &a, const std::bitset<16> &b)
{
EXPECT_EQ(a, b);
Expand Down Expand Up @@ -33,14 +40,38 @@ void check_unordered_multimap(const std::unordered_multimap<std::string, std::ve
EXPECT_EQ(a, b);
}

template <typename T>
T interpreter_get(const char *expr)
{
T *ptr = reinterpret_cast<T *>(gInterpreter->Calc(expr));
EXPECT_NE(ptr, nullptr);
return *ptr;
}

TEST(RNTupleMakeProject, ReadBackTTree)
{
ASSERT_EQ(gSystem->Load("libttreestltest/libttreestltest"), 0);

std::unique_ptr<TFile> f{TFile::Open("ntuple_makeproject_stl_example_ttree.root")};
std::unique_ptr<TTree> t{f->Get<TTree>("events")};

MySTLEvent *event{nullptr};
t->SetBranchAddress("test", &event);
t->GetEntry(0);
// Expose the TTree pointer to the interpreter
gInterpreter->ProcessLine(TString::Format("TTree *t = (TTree *)0x%zx;", reinterpret_cast<std::size_t>(t.get())));

// Everything involving MySTLEvent lives in the interpreter
gInterpreter->ProcessLine(R"(

MySTLEvent *event{nullptr};
t->SetBranchAddress("test", &event);
t->GetEntry(0);
)");

// Get values out as plain STL types
auto event_foo = interpreter_get<std::bitset<16>>("&event->foo");
auto event_bar = interpreter_get<std::vector<int>>("&event->bar");
auto event_spam = interpreter_get<std::unordered_set<double>>("&event->spam");
auto event_eggs = interpreter_get<std::unordered_map<int, std::string>>("&event->eggs");
auto event_strange = interpreter_get<std::unordered_multimap<std::string, std::vector<float>>>("&event->strange");

std::bitset<16> foo = 0xfa2;
std::vector<int> bar = {1, 2};
Expand All @@ -49,11 +80,11 @@ TEST(RNTupleMakeProject, ReadBackTTree)
std::unordered_multimap<std::string, std::vector<float>> strange = {
{"one", {1, 2, 3}}, {"one", {4, 5, 6}}, {"two", {7, 8, 9}}};

check_bitset(event->foo, foo);
check_vector(event->bar, bar);
check_unordered_set(event->spam, spam);
check_unordered_map(event->eggs, eggs);
check_unordered_multimap(event->strange, strange);
check_bitset(event_foo, foo);
check_vector(event_bar, bar);
check_unordered_set(event_spam, spam);
check_unordered_map(event_eggs, eggs);
check_unordered_multimap(event_strange, strange);
}

int main(int argc, char **argv)
Expand Down
Loading