diff --git a/test/form/CMakeLists.txt b/test/form/CMakeLists.txt
index d126e9ed5..8c605504b 100644
--- a/test/form/CMakeLists.txt
+++ b/test/form/CMakeLists.txt
@@ -31,6 +31,52 @@ if(FORM_USE_ROOT_STORAGE)
WriteVector
)
target_include_directories(ReadVector PRIVATE ${PROJECT_SOURCE_DIR}/form)
+
+ cet_test(
+ form_root_schema_write_test
+ SOURCE
+ form_root_schema_write_test.cpp
+ toy_tracker.cpp
+ LIBRARIES
+ form
+ form_test_data_products
+ TEST_WORKDIR
+ form_root_schema_test
+ )
+ target_include_directories(form_root_schema_write_test PRIVATE ${PROJECT_SOURCE_DIR}/form)
+
+ cet_test(
+ form_root_schema_read_test
+ SOURCE
+ form_root_schema_read_test.cpp
+ LIBRARIES
+ form
+ form_test_data_products_schema_evolution
+ DIRTY_WORKDIR
+ TEST_WORKDIR
+ form_root_schema_test
+ REQUIRED_FIXTURES
+ form_root_schema_write_test
+ )
+ target_include_directories(form_root_schema_read_test PRIVATE ${PROJECT_SOURCE_DIR}/form)
+
+ cet_test(
+ check_root_schema_evolution
+ HANDBUILT
+ TEST_EXEC
+ ${CMAKE_COMMAND}
+ TEST_ARGS
+ -E
+ compare_files
+ form_root_schema_write_log.txt
+ form_root_schema_read_log.txt
+ DIRTY_WORKDIR
+ TEST_WORKDIR
+ form_root_schema_test
+ REQUIRED_FIXTURES
+ form_root_schema_write_test
+ form_root_schema_read_test
+ )
endif()
cet_test(
diff --git a/test/form/data_products/CMakeLists.txt b/test/form/data_products/CMakeLists.txt
index 405951815..3bf154308 100644
--- a/test/form/data_products/CMakeLists.txt
+++ b/test/form/data_products/CMakeLists.txt
@@ -8,4 +8,6 @@ if(FORM_USE_ROOT_STORAGE)
${FORM_DATA_PROD_LIB_NAME} dict.h SELECTION classes_def.xml
)
target_link_libraries(${FORM_DATA_PROD_LIB_NAME} PRIVATE ROOT::RIO)
+
+ add_subdirectory(extra_member)
endif()
diff --git a/test/form/data_products/extra_member/CMakeLists.txt b/test/form/data_products/extra_member/CMakeLists.txt
new file mode 100644
index 000000000..b422a196f
--- /dev/null
+++ b/test/form/data_products/extra_member/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(FORM_EXTRA_DATA_PROD_LIB_NAME "form_test_data_products_schema_evolution")
+add_library(${FORM_EXTRA_DATA_PROD_LIB_NAME} SHARED track_start.cpp)
+
+if(FORM_USE_ROOT_STORAGE)
+ find_package(ROOT REQUIRED COMPONENTS RIO)
+ include_directories(${ROOT_INCLUDE_DIRS})
+ reflex_generate_dictionary(
+ ${FORM_EXTRA_DATA_PROD_LIB_NAME} dict.h SELECTION classes_def.xml
+ )
+ target_link_libraries(${FORM_EXTRA_DATA_PROD_LIB_NAME} PRIVATE ROOT::RIO)
+endif()
diff --git a/test/form/data_products/extra_member/classes_def.xml b/test/form/data_products/extra_member/classes_def.xml
new file mode 100644
index 000000000..249f2d09b
--- /dev/null
+++ b/test/form/data_products/extra_member/classes_def.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/test/form/data_products/extra_member/dict.h b/test/form/data_products/extra_member/dict.h
new file mode 100644
index 000000000..42162db0c
--- /dev/null
+++ b/test/form/data_products/extra_member/dict.h
@@ -0,0 +1,2 @@
+#include "track_start.hpp"
+#include
diff --git a/test/form/data_products/extra_member/track_start.cpp b/test/form/data_products/extra_member/track_start.cpp
new file mode 100644
index 000000000..4ebe3511a
--- /dev/null
+++ b/test/form/data_products/extra_member/track_start.cpp
@@ -0,0 +1,49 @@
+#include "track_start.hpp"
+
+TrackStart::TrackStart() : m_x(0), m_y(0), m_z(0), m_index(0) {}
+
+TrackStart::TrackStart(float x, float y, float z, int index) :
+ m_x(x), m_y(y), m_z(z), m_index(index)
+{
+}
+
+float TrackStart::getX() const { return m_x; }
+
+float TrackStart::getY() const { return m_y; }
+
+float TrackStart::getZ() const { return m_z; }
+
+int TrackStart::getIndex() const { return m_index; }
+
+void TrackStart::setX(float x) { m_x = x; }
+
+void TrackStart::setY(float y) { m_y = y; }
+
+void TrackStart::setZ(float z) { m_z = z; }
+
+void TrackStart::setIndex(int index) { m_index = index; }
+
+TrackStart TrackStart::operator+(TrackStart const& other) const
+{
+ return TrackStart(m_x + other.m_x, m_y + other.m_y, m_z + other.m_z, m_index + other.m_index);
+}
+
+TrackStart& TrackStart::operator+=(TrackStart const& other)
+{
+ m_x += other.m_x;
+ m_y += other.m_y;
+ m_z += other.m_z;
+ m_index += other.m_index;
+ return *this;
+}
+
+TrackStart TrackStart::operator-(TrackStart const& other) const
+{
+ return TrackStart(m_x - other.m_x, m_y - other.m_y, m_z - other.m_z, m_index - other.m_index);
+}
+
+std::ostream& operator<<(std::ostream& os, TrackStart const& track)
+{
+ os << "TrackStart{" << track.getX() << ", " << track.getY() << ", " << track.getZ() << "}";
+ return os;
+}
diff --git a/test/form/data_products/extra_member/track_start.hpp b/test/form/data_products/extra_member/track_start.hpp
new file mode 100644
index 000000000..b93e744a6
--- /dev/null
+++ b/test/form/data_products/extra_member/track_start.hpp
@@ -0,0 +1,39 @@
+//A TrackStart is a 3-vector of position components.
+//This is a simple test data product for demonstrating the features of FORM.
+
+#include
+
+#ifndef TRACKSTART_HPP
+#define TRACKSTART_HPP
+
+class TrackStart {
+public:
+ TrackStart();
+ TrackStart(float x, float y, float z, int index);
+ ~TrackStart() = default;
+
+ float getX() const;
+ float getY() const;
+ float getZ() const;
+ int getIndex() const;
+
+ void setX(float x);
+ void setY(float y);
+ void setZ(float z);
+ void setIndex(int index);
+
+ TrackStart operator+(TrackStart const& other) const;
+ TrackStart& operator+=(TrackStart const& other);
+ TrackStart operator-(TrackStart const& other) const;
+
+private:
+ float m_x;
+ float m_y;
+ float m_z;
+
+ int m_index;
+};
+
+std::ostream& operator<<(std::ostream& os, TrackStart const& track);
+
+#endif //TRACKSTART_HPP
diff --git a/test/form/form_root_schema_read_test.cpp b/test/form/form_root_schema_read_test.cpp
new file mode 100644
index 000000000..1840076df
--- /dev/null
+++ b/test/form/form_root_schema_read_test.cpp
@@ -0,0 +1,24 @@
+//Part two of schema evolution unit test: can we build a program against a new dictionary for the same product that reads the old product and gets back the correct values?
+
+#include "test/form/data_products/extra_member/track_start.hpp"
+#include "test/form/test_utils.hpp"
+
+#include
+#include
+#include
+
+using namespace form::test;
+
+int main(int const argc, char const** argv)
+{
+ int const technology = getTechnology(argc, argv);
+ if (technology < 0)
+ return 1;
+
+ auto const& [prods] = read>(technology);
+ std::ofstream outFile("form_root_schema_read_log.txt");
+ for (auto const& prod : *prods)
+ outFile << prod << std::endl;
+
+ return 0;
+}
diff --git a/test/form/form_root_schema_write_test.cpp b/test/form/form_root_schema_write_test.cpp
new file mode 100644
index 000000000..d5574502a
--- /dev/null
+++ b/test/form/form_root_schema_write_test.cpp
@@ -0,0 +1,29 @@
+//Schema evolution test component: write old version of a data product to disk
+
+#include "test/form/data_products/track_start.hpp"
+#include "test/form/test_utils.hpp"
+#include "test/form/toy_tracker.hpp"
+
+#include
+#include
+#include
+
+using namespace form::test;
+
+int main(int const argc, char const** argv)
+{
+ int const technology = getTechnology(argc, argv);
+ if (technology < 0)
+ return 1;
+
+ ToyTracker tracker(4 * 1024);
+ std::vector const prods = tracker();
+
+ std::ofstream outFile("form_root_schema_write_log.txt");
+ for (auto const& prod : prods)
+ outFile << prod << std::endl;
+
+ write(technology, prods);
+
+ return 0;
+}