Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e5dc226
Add a unit test requiring that FORM throws an exception when trying to
aolivier23 Feb 23, 2026
10cd360
Added test that two FORM AssociativeContainers can share the same
aolivier23 Feb 23, 2026
8a83a00
Added FORM test that three Associative_Containers can share the same
aolivier23 Feb 23, 2026
4d27dc1
Apply clang-format fixes
github-actions[bot] Feb 23, 2026
ff47309
Added FORM tests for error handling when setting up Containers.
aolivier23 Feb 23, 2026
0388f10
Apply clang-format fixes
github-actions[bot] Feb 23, 2026
747a803
Made form::test::read<>() return std::unique_ptr<> instead of copies of
aolivier23 Feb 24, 2026
e241cd2
Merge branch 'feature/aolivier-form-storage-tests' of https://github.…
aolivier23 Feb 24, 2026
822feff
Apply clang-format fixes
github-actions[bot] Feb 24, 2026
a616793
Updated (so-far-unused) form::test::getTechnology() to use
aolivier23 Feb 24, 2026
a6b2239
Merge branch 'feature/aolivier-form-storage-tests' of https://github.…
aolivier23 Feb 24, 2026
f816ae8
Made all of test_utils.hpp inline in case someone writes a test
aolivier23 Feb 24, 2026
5ea6eda
Merge branch 'main' into feature/aolivier-form-storage-tests
aolivier23 Feb 26, 2026
163399e
Moved FORM's USE_ROOT_STORAGE compile definition to somewhere where it
aolivier23 Feb 26, 2026
4352b16
Merge branch 'main' into feature/aolivier-form-storage-tests
aolivier23 Feb 27, 2026
a586242
Updated include guards in test_utils.hpp to match new convention on
aolivier23 Feb 27, 2026
6ca3fee
Removed unneeded assert()s from FORM Storage test.
aolivier23 Feb 27, 2026
09ea1af
Merge branch 'main' into feature/aolivier-form-storage-tests
aolivier23 Feb 27, 2026
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
7 changes: 7 additions & 0 deletions test/form/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (C) 2025 ...

if(FORM_USE_ROOT_STORAGE)
add_definitions(-DUSE_ROOT_STORAGE) #TODO: This doesn't carry over from FORM for some reason. Why? Can tests get definitions from properties of targets they link against?
add_subdirectory(data_products)
cet_test(
WriteVector
Expand Down Expand Up @@ -48,3 +49,9 @@ cet_test(form_basics_test USE_CATCH2_MAIN SOURCE form_basics_test.cpp LIBRARIES
form
)
target_include_directories(form_basics_test PRIVATE ${PROJECT_SOURCE_DIR}/form)

cet_test(form_storage_test USE_CATCH2_MAIN SOURCE form_storage_test.cpp LIBRARIES
root_storage
storage
)
target_include_directories(form_storage_test PRIVATE ${PROJECT_SOURCE_DIR}/form)
109 changes: 109 additions & 0 deletions test/form/form_storage_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//Tests for FORM's storage layer's design requirements

#include "test/form/test_utils.hpp"

#include <catch2/catch_test_macros.hpp>

#include <cmath>
#include <numeric>
#include <vector>

using namespace form::detail::experimental;

TEST_CASE("Storage_Container read wrong type", "[form]")
{
int const technology = form::technology::ROOT_TTREE;
std::vector<int> primes = {2, 3, 5, 7, 11, 13, 17, 19};
form::test::write(technology, primes);

auto file = createFile(technology, form::test::testFileName, 'i');
auto container = createContainer(technology, form::test::makeTestBranchName<std::vector<int>>());
container->setFile(file);
void const* dataPtr;
CHECK_THROWS_AS(container->read(0, &dataPtr, typeid(double)), std::runtime_error);
}

TEST_CASE("Storage_Container sharing an Association", "[form]")
{
int const technology = form::technology::ROOT_TTREE;
std::vector<float> piData(10, 3.1415927);
std::string indexData = "[EVENT=00000001;SEG=00000001]";

form::test::write(technology, piData, indexData);

auto [piResult, indexResult] = form::test::read<std::vector<float>, std::string>(technology);

float const originalSum = std::accumulate(piData.begin(), piData.end(), 0.f);
float const readSum = std::accumulate(piResult.begin(), piResult.end(), 0.f);
float const floatDiff = readSum - originalSum;

SECTION("float container sum") { CHECK(fabs(floatDiff) < std::numeric_limits<float>::epsilon()); }

SECTION("index") { CHECK(indexResult == indexData); }
}

TEST_CASE("Storage_Container multiple containers in Association", "[form]")
{
int const technology = form::technology::ROOT_TTREE;
std::vector<float> piData(10, 3.1415927);
std::vector<int> magicData(17);
std::iota(magicData.begin(), magicData.end(), 42);
std::string indexData = "[EVENT=00000001;SEG=00000001]";

form::test::write(technology, piData, magicData, indexData);

auto [piResult, magicResult, indexResult] =
form::test::read<std::vector<float>, std::vector<int>, std::string>(technology);

SECTION("float container")
{
float const originalSum = std::accumulate(piData.begin(), piData.end(), 0.f);
float const readSum = std::accumulate(piResult.begin(), piResult.end(), 0.f);
float const floatDiff = readSum - originalSum;
CHECK(fabs(floatDiff) < std::numeric_limits<float>::epsilon());
}

SECTION("int container")
{
int const originalMagic = std::accumulate(magicData.begin(), magicData.end(), 0);
int const readMagic = std::accumulate(magicResult.begin(), magicResult.end(), 0);
int const magicDiff = readMagic - originalMagic;
CHECK(magicDiff == 0);
}

SECTION("index data") { CHECK(indexResult == indexData); }
}

TEST_CASE("FORM Container setup error handling")
{
int const technology = form::technology::ROOT_TTREE;
auto file = createFile(technology, "testContainerErrorHandling.root", 'o');
auto container = createContainer(technology, "test/testData");

std::vector<float> testData;
void const* ptrTestData = &testData;
auto const& typeInfo = typeid(testData);

SECTION("fill() before setParent()")
{
CHECK_THROWS_AS(container->setupWrite(typeInfo), std::runtime_error);
CHECK_THROWS_AS(container->fill(ptrTestData), std::runtime_error);
}

SECTION("commit() before setParent()")
{
CHECK_THROWS_AS(container->commit(), std::runtime_error);
}

SECTION("read() before setParent()")
{
CHECK_THROWS_AS(container->read(0, &ptrTestData, typeInfo), std::runtime_error);
}

SECTION("mismatched file type")
{
std::shared_ptr<IStorage_File> wrongFile(
new Storage_File("testContainerErrorHandling.root", 'o'));
CHECK_THROWS_AS(container->setFile(wrongFile), std::runtime_error);
}
}
126 changes: 126 additions & 0 deletions test/form/test_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//Utilities to make FORM unit tests easier to write and maintain

#ifndef __TEST_UTILS_HPP__
#define __TEST_UTILS_HPP__

#include "storage/istorage.hpp"
#include "storage/storage_associative_container.hpp"
#include "util/factories.hpp"

#include "TClass.h"

#include <iostream>
#include <memory>

using namespace form::detail::experimental;

namespace form::test {

std::string const testTreeName = "FORMTestTree";
std::string const testFileName = "FORMTestFile.root";

template <class PROD>
std::string getTypeName()
{
return TClass::GetClass<PROD>()->GetName();
}

template <class PROD>
std::string makeTestBranchName()
{
return testTreeName + "/" + getTypeName<PROD>();
}

std::vector<std::shared_ptr<IStorage_Container>> doWrite(
std::shared_ptr<IStorage_File>& /*file*/,
int const /*technology*/,
std::shared_ptr<IStorage_Container>& /*parent*/)
{
return std::vector<std::shared_ptr<IStorage_Container>>();
}

template <class PROD, class... PRODS>
std::vector<std::shared_ptr<IStorage_Container>> doWrite(
std::shared_ptr<IStorage_File>& file,
int const technology,
std::shared_ptr<IStorage_Container>& parent,
PROD& prod,
PRODS&... prods)
{
auto const branchName = makeTestBranchName<PROD>();
auto container = createContainer(technology, branchName);
auto assoc = dynamic_pointer_cast<Storage_Associative_Container>(container);
if (assoc) {
assoc->setParent(parent);
}
container->setFile(file);
container->setupWrite(typeid(PROD));

auto result = doWrite(file, technology, parent, prods...);
container->fill(&prod); //This must happen after setupWrite()
result.push_back(container);
return result;
}

template <class... PRODS>
void write(int const technology, PRODS&... prods)
{
auto file = createFile(technology, testFileName.c_str(), 'o');
auto parent = createAssociation(technology, testTreeName);
parent->setFile(file);
parent->setupWrite();

auto keepContainersAlive = doWrite(file, technology, parent, prods...);
keepContainersAlive.back()
->commit(); //Elements are in reverse order of container construction, so this makes sure container owner calls commit()
}

template <class PROD>
PROD doRead(std::shared_ptr<IStorage_File>& file,
int const technology,
std::shared_ptr<IStorage_Container>& parent)
{
auto container = createContainer(technology, makeTestBranchName<PROD>());
auto assoc = dynamic_pointer_cast<Storage_Associative_Container>(container);
if (assoc) {
assoc->setParent(parent);
}
container->setFile(file);
void const* dataPtr = new PROD();

if (!container->read(0, &dataPtr, typeid(PROD)))
throw std::runtime_error("Failed to read a " + getTypeName<PROD>());

return *static_cast<const PROD*>(dataPtr);
}

template <class... PRODS>
std::tuple<PRODS...> read(int const technology)
{
auto file = createFile(technology, testFileName, 'i');
auto parent = createAssociation(technology, testTreeName);
parent->setFile(file);

return std::make_tuple(doRead<PRODS>(file, technology, parent)...);
}

int getTechnology(int const argc, char const** argv)
{
if (argc > 2 || (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")))) {
std::cerr << "Expected exactly one argument, but got " << argc - 1 << "\n"
<< "USAGE: testSchemaWriteOldProduct <technologyInt>\n";
return -1;
}

//Default to TTree with TFile
int technology = 256 * 1 + 1;

if (argc == 2)
technology = std::stoi(argv[1]);

return technology;
}

} // namespace form::test

#endif //__TEST_UTILS_HPP__
Loading