Skip to content

Commit 0435987

Browse files
FORM Storage Tests (#354)
* Add a unit test requiring that FORM throws an exception when trying to read a data product from disk with a type that doesn't match the type on disk. * Added test that two FORM AssociativeContainers can share the same Association. * Added FORM test that three Associative_Containers can share the same Association. - This models a physics algorithm in phlex writing two data products through FORM. * Added FORM tests for error handling when setting up Containers. * Made form::test::read<>() return std::unique_ptr<> instead of copies of dangling pointers. * Updated (so-far-unused) form::test::getTechnology() to use form::technology::ROOT_TTREE for default instead of raw integers for better maintainability. * Made all test_utils.hpp inline in case someone writes a test complicated enough that it gets included twice. * Moved FORM's USE_ROOT_STORAGE compile definition to somewhere where it propagates automatically: the root_storage library itself. * Updated include guards in test_utils.hpp to match new convention on main. * Removed unneeded assert()s from FORM Storage test. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 9bca2a6 commit 0435987

File tree

5 files changed

+243
-3
lines changed

5 files changed

+243
-3
lines changed

form/CMakeLists.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ include_directories(${PROJECT_SOURCE_DIR}/form)
1414

1515
# ROOT Storage toggle
1616
option(FORM_USE_ROOT_STORAGE "Enable ROOT Storage" ON)
17-
if(FORM_USE_ROOT_STORAGE)
18-
add_definitions(-DUSE_ROOT_STORAGE)
19-
endif()
2017

2118
# Add sub directories
2219
add_subdirectory(form)

form/root_storage/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ find_package(ROOT REQUIRED COMPONENTS Core RIO Tree)
55

66
# Component(s) in the package:
77
add_library(root_storage root_tfile.cpp root_ttree_container.cpp root_tbranch_container.cpp)
8+
target_compile_definitions(root_storage PUBLIC USE_ROOT_STORAGE)
89

910
# Link the ROOT libraries
1011
target_link_libraries(root_storage PUBLIC ROOT::Core ROOT::RIO ROOT::Tree storage)

test/form/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,9 @@ cet_test(form_basics_test USE_CATCH2_MAIN SOURCE form_basics_test.cpp LIBRARIES
5050
form
5151
)
5252
target_include_directories(form_basics_test PRIVATE ${PROJECT_SOURCE_DIR}/form)
53+
54+
cet_test(form_storage_test USE_CATCH2_MAIN SOURCE form_storage_test.cpp LIBRARIES
55+
root_storage
56+
storage
57+
)
58+
target_include_directories(form_storage_test PRIVATE ${PROJECT_SOURCE_DIR}/form)

test/form/form_storage_test.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//Tests for FORM's storage layer's design requirements
2+
3+
#include "test/form/test_utils.hpp"
4+
5+
#include <catch2/catch_test_macros.hpp>
6+
7+
#include <cmath>
8+
#include <numeric>
9+
#include <vector>
10+
11+
using namespace form::detail::experimental;
12+
13+
TEST_CASE("Storage_Container read wrong type", "[form]")
14+
{
15+
int const technology = form::technology::ROOT_TTREE;
16+
std::vector<int> primes = {2, 3, 5, 7, 11, 13, 17, 19};
17+
form::test::write(technology, primes);
18+
19+
auto file = createFile(technology, form::test::testFileName, 'i');
20+
auto container = createContainer(technology, form::test::makeTestBranchName<std::vector<int>>());
21+
container->setFile(file);
22+
void const* dataPtr;
23+
CHECK_THROWS_AS(container->read(0, &dataPtr, typeid(double)), std::runtime_error);
24+
}
25+
26+
TEST_CASE("Storage_Container sharing an Association", "[form]")
27+
{
28+
int const technology = form::technology::ROOT_TTREE;
29+
std::vector<float> piData(10, 3.1415927);
30+
std::string indexData = "[EVENT=00000001;SEG=00000001]";
31+
32+
form::test::write(technology, piData, indexData);
33+
34+
auto [piResult, indexResult] = form::test::read<std::vector<float>, std::string>(technology);
35+
36+
float const originalSum = std::accumulate(piData.begin(), piData.end(), 0.f);
37+
float const readSum = std::accumulate(piResult->begin(), piResult->end(), 0.f);
38+
float const floatDiff = readSum - originalSum;
39+
40+
SECTION("float container sum") { CHECK(fabs(floatDiff) < std::numeric_limits<float>::epsilon()); }
41+
42+
SECTION("index") { CHECK(*indexResult == indexData); }
43+
}
44+
45+
TEST_CASE("Storage_Container multiple containers in Association", "[form]")
46+
{
47+
int const technology = form::technology::ROOT_TTREE;
48+
std::vector<float> piData(10, 3.1415927);
49+
std::vector<int> magicData(17);
50+
std::iota(magicData.begin(), magicData.end(), 42);
51+
std::string indexData = "[EVENT=00000001;SEG=00000001]";
52+
53+
form::test::write(technology, piData, magicData, indexData);
54+
55+
auto [piResult, magicResult, indexResult] =
56+
form::test::read<std::vector<float>, std::vector<int>, std::string>(technology);
57+
58+
SECTION("float container")
59+
{
60+
float const originalSum = std::accumulate(piData.begin(), piData.end(), 0.f);
61+
float const readSum = std::accumulate(piResult->begin(), piResult->end(), 0.f);
62+
float const floatDiff = readSum - originalSum;
63+
CHECK(fabs(floatDiff) < std::numeric_limits<float>::epsilon());
64+
}
65+
66+
SECTION("int container")
67+
{
68+
int const originalMagic = std::accumulate(magicData.begin(), magicData.end(), 0);
69+
int const readMagic = std::accumulate(magicResult->begin(), magicResult->end(), 0);
70+
int const magicDiff = readMagic - originalMagic;
71+
CHECK(magicDiff == 0);
72+
}
73+
74+
SECTION("index data") { CHECK(*indexResult == indexData); }
75+
}
76+
77+
TEST_CASE("FORM Container setup error handling")
78+
{
79+
int const technology = form::technology::ROOT_TTREE;
80+
auto file = createFile(technology, "testContainerErrorHandling.root", 'o');
81+
auto container = createContainer(technology, "test/testData");
82+
83+
std::vector<float> testData;
84+
void const* ptrTestData = &testData;
85+
auto const& typeInfo = typeid(testData);
86+
87+
SECTION("fill() before setParent()")
88+
{
89+
CHECK_THROWS_AS(container->setupWrite(typeInfo), std::runtime_error);
90+
CHECK_THROWS_AS(container->fill(ptrTestData), std::runtime_error);
91+
}
92+
93+
SECTION("commit() before setParent()")
94+
{
95+
CHECK_THROWS_AS(container->commit(), std::runtime_error);
96+
}
97+
98+
SECTION("read() before setParent()")
99+
{
100+
CHECK_THROWS_AS(container->read(0, &ptrTestData, typeInfo), std::runtime_error);
101+
}
102+
103+
SECTION("mismatched file type")
104+
{
105+
std::shared_ptr<IStorage_File> wrongFile(
106+
new Storage_File("testContainerErrorHandling.root", 'o'));
107+
CHECK_THROWS_AS(container->setFile(wrongFile), std::runtime_error);
108+
}
109+
}

test/form/test_utils.hpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
//Utilities to make FORM unit tests easier to write and maintain
2+
3+
#ifndef FORM_TEST_UTILS_HPP
4+
#define FORM_TEST_UTILS_HPP
5+
6+
#include "storage/istorage.hpp"
7+
#include "storage/storage_associative_container.hpp"
8+
#include "util/factories.hpp"
9+
10+
#include "TClass.h"
11+
12+
#include <iostream>
13+
#include <memory>
14+
15+
using namespace form::detail::experimental;
16+
17+
namespace form::test {
18+
19+
inline std::string const testTreeName = "FORMTestTree";
20+
inline std::string const testFileName = "FORMTestFile.root";
21+
22+
template <class PROD>
23+
inline std::string getTypeName()
24+
{
25+
return TClass::GetClass<PROD>()->GetName();
26+
}
27+
28+
template <class PROD>
29+
inline std::string makeTestBranchName()
30+
{
31+
return testTreeName + "/" + getTypeName<PROD>();
32+
}
33+
34+
inline std::vector<std::shared_ptr<IStorage_Container>> doWrite(
35+
std::shared_ptr<IStorage_File>& /*file*/,
36+
int const /*technology*/,
37+
std::shared_ptr<IStorage_Container>& /*parent*/)
38+
{
39+
return std::vector<std::shared_ptr<IStorage_Container>>();
40+
}
41+
42+
template <class PROD, class... PRODS>
43+
inline std::vector<std::shared_ptr<IStorage_Container>> doWrite(
44+
std::shared_ptr<IStorage_File>& file,
45+
int const technology,
46+
std::shared_ptr<IStorage_Container>& parent,
47+
PROD& prod,
48+
PRODS&... prods)
49+
{
50+
auto const branchName = makeTestBranchName<PROD>();
51+
auto container = createContainer(technology, branchName);
52+
auto assoc = dynamic_pointer_cast<Storage_Associative_Container>(container);
53+
if (assoc) {
54+
assoc->setParent(parent);
55+
}
56+
container->setFile(file);
57+
container->setupWrite(typeid(PROD));
58+
59+
auto result = doWrite(file, technology, parent, prods...);
60+
container->fill(&prod); //This must happen after setupWrite()
61+
result.push_back(container);
62+
return result;
63+
}
64+
65+
template <class... PRODS>
66+
inline void write(int const technology, PRODS&... prods)
67+
{
68+
auto file = createFile(technology, testFileName.c_str(), 'o');
69+
auto parent = createAssociation(technology, testTreeName);
70+
parent->setFile(file);
71+
parent->setupWrite();
72+
73+
auto keepContainersAlive = doWrite(file, technology, parent, prods...);
74+
keepContainersAlive.back()
75+
->commit(); //Elements are in reverse order of container construction, so this makes sure container owner calls commit()
76+
}
77+
78+
template <class PROD>
79+
inline std::unique_ptr<PROD const> doRead(std::shared_ptr<IStorage_File>& file,
80+
int const technology,
81+
std::shared_ptr<IStorage_Container>& parent)
82+
{
83+
auto container = createContainer(technology, makeTestBranchName<PROD>());
84+
auto assoc = dynamic_pointer_cast<Storage_Associative_Container>(container);
85+
if (assoc) {
86+
assoc->setParent(parent);
87+
}
88+
container->setFile(file);
89+
void const* dataPtr = new PROD();
90+
void const** dataPtrPtr = &dataPtr;
91+
92+
if (!container->read(0, dataPtrPtr, typeid(PROD)))
93+
throw std::runtime_error("Failed to read a " + getTypeName<PROD>());
94+
95+
return std::unique_ptr<PROD const>(static_cast<PROD const*>(dataPtr));
96+
}
97+
98+
template <class... PRODS>
99+
inline std::tuple<std::unique_ptr<PRODS const>...> read(int const technology)
100+
{
101+
auto file = createFile(technology, testFileName, 'i');
102+
auto parent = createAssociation(technology, testTreeName);
103+
parent->setFile(file);
104+
105+
return std::make_tuple(doRead<PRODS>(file, technology, parent)...);
106+
}
107+
108+
inline int getTechnology(int const argc, char const** argv)
109+
{
110+
if (argc > 2 || (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")))) {
111+
std::cerr << "Expected exactly one argument, but got " << argc - 1 << "\n"
112+
<< "USAGE: testSchemaWriteOldProduct <technologyInt>\n";
113+
return -1;
114+
}
115+
116+
//Default to TTree with TFile
117+
int technology = form::technology::ROOT_TTREE;
118+
119+
if (argc == 2)
120+
technology = std::stoi(argv[1]);
121+
122+
return technology;
123+
}
124+
125+
} // namespace form::test
126+
127+
#endif // FORM_TEST_UTILS_HPP

0 commit comments

Comments
 (0)