LCOV - code coverage report
Current view: top level - dfmodules/unittest - SummaryTextWrite_test.cxx (source / functions) Coverage Total Hit
Test: code.result Lines: 100.0 % 215 215
Test Date: 2026-10-11 15:05:20 Functions: 100.0 % 24 24

            Line data    Source code
       1              : /**
       2              :  * @file SummaryTextWrite_test.cxx Application that ostensibly tests and
       3              :  * demonstrates the write functionality of the SummaryTextDataStore class,
       4              :  * but can be more properly thought of as a test of the base class
       5              :  * from which it inherits, FileDataStoreImpl
       6              :  *
       7              :  * This is heavily modeled on the HDF5Write unit tests, with
       8              :  * adjustments where necessary (max file size different in the
       9              :  * multiple-output-file test; no check for a ".writing" suffix for
      10              :  * files in the process of being written to)
      11              :  *
      12              :  * This is part of the DUNE DAQ Application Framework, copyright 2020.
      13              :  * Licensing/copyright details are in the COPYING file that you should have
      14              :  * received with this code.
      15              :  */
      16              : 
      17              : #include "dfmodules/DataStore.hpp"
      18              : #include "dfmodules/DataStoreConfTestDeriv.hpp"
      19              : 
      20              : // We need the actual SummaryTextDataStore.hpp plugin header so the unit tests can access its exceptions
      21              : #include "../plugins/SummaryTextDataStore.hpp" // NOLINT(build/include_path)
      22              : 
      23              : #include "appmodel/DataStoreConf.hpp"
      24              : #include "appmodel/DataWriterConf.hpp"
      25              : #include "appmodel/DataWriterModule.hpp"
      26              : #include "appmodel/FilenameParams.hpp"
      27              : #include "confmodel/DetectorConfig.hpp"
      28              : #include "confmodel/Session.hpp"
      29              : #include "detdataformats/DetID.hpp"
      30              : 
      31              : #define BOOST_TEST_MODULE SummaryTextWrite_test // NOLINT
      32              : 
      33              : #include "boost/test/unit_test.hpp"
      34              : 
      35              : #include <cstdlib>
      36              : #include <filesystem>
      37              : #include <fstream>
      38              : #include <iostream>
      39              : #include <limits>
      40              : #include <memory>
      41              : #include <regex>
      42              : #include <string>
      43              : #include <utility>
      44              : #include <vector>
      45              : 
      46              : using namespace dunedaq::dfmodules;
      47              : 
      48              : std::vector<std::string>
      49            5 : get_files_matching_pattern(const std::string& path, const std::string& pattern)
      50              : {
      51            5 :   std::regex regex_search_pattern(pattern);
      52            5 :   std::vector<std::string> file_list;
      53         2087 :   for (const auto& entry : std::filesystem::directory_iterator(path)) {
      54         2082 :     if (std::regex_match(entry.path().filename().string(), regex_search_pattern)) {
      55           27 :       file_list.push_back(entry.path());
      56              :     }
      57            5 :   }
      58            5 :   return file_list;
      59            5 : }
      60              : 
      61              : std::vector<std::string>
      62           14 : delete_files_matching_pattern(const std::string& path, const std::string& pattern)
      63              : {
      64           14 :   std::regex regex_search_pattern(pattern);
      65           14 :   std::vector<std::string> file_list;
      66         5795 :   for (const auto& entry : std::filesystem::directory_iterator(path)) {
      67         5781 :     if (std::regex_match(entry.path().filename().string(), regex_search_pattern)) {
      68           27 :       if (std::filesystem::remove(entry.path())) {
      69           27 :         file_list.push_back(entry.path());
      70              :       }
      71              :     }
      72           14 :   }
      73           14 :   return file_list;
      74           14 : }
      75              : 
      76              : dunedaq::daqdataformats::TriggerRecord
      77           30 : create_trigger_record(int trig_num, int fragment_size, int element_count)
      78              : {
      79           30 :   const int run_number = 53;
      80           30 :   const dunedaq::daqdataformats::SourceID::Subsystem stype_to_use =
      81              :     dunedaq::daqdataformats::SourceID::Subsystem::kDetectorReadout;
      82              : 
      83              :   // setup our dummy_data
      84           30 :   std::vector<char> dummy_vector(fragment_size);
      85           30 :   char* dummy_data = dummy_vector.data();
      86              : 
      87              :   // get a timestamp for this trigger
      88           60 :   uint64_t ts = std::chrono::duration_cast<std::chrono::milliseconds>( // NOLINT(build/unsigned)
      89           30 :                   system_clock::now().time_since_epoch())
      90           30 :                   .count();
      91              : 
      92              :   // create TriggerRecordHeader
      93           30 :   dunedaq::daqdataformats::TriggerRecordHeaderData trh_data;
      94           30 :   trh_data.trigger_number = trig_num;
      95           30 :   trh_data.trigger_timestamp = ts;
      96           30 :   trh_data.num_requested_components = element_count;
      97           30 :   trh_data.run_number = run_number;
      98           30 :   trh_data.sequence_number = 0;
      99           30 :   trh_data.max_sequence_number = 1;
     100           30 :   trh_data.element_id = dunedaq::daqdataformats::SourceID(dunedaq::daqdataformats::SourceID::Subsystem::kTRBuilder, 0);
     101              : 
     102           30 :   dunedaq::daqdataformats::TriggerRecordHeader trh(&trh_data);
     103              : 
     104              :   // create out TriggerRecord
     105           30 :   dunedaq::daqdataformats::TriggerRecord tr(trh);
     106              : 
     107              :   // loop over elements
     108          160 :   for (int ele_num = 0; ele_num < element_count; ++ele_num) {
     109              : 
     110              :     // create our fragment
     111          130 :     dunedaq::daqdataformats::FragmentHeader fh;
     112          130 :     fh.trigger_number = trig_num;
     113          130 :     fh.trigger_timestamp = ts;
     114          130 :     fh.window_begin = ts - 10;
     115          130 :     fh.window_end = ts;
     116          130 :     fh.run_number = run_number;
     117          130 :     fh.sequence_number = 0;
     118          130 :     fh.fragment_type =
     119              :       static_cast<dunedaq::daqdataformats::fragment_type_t>(dunedaq::daqdataformats::FragmentType::kWIB);
     120          130 :     fh.detector_id =
     121              :       static_cast<uint16_t>(dunedaq::detdataformats::DetID::Subdetector::kHD_TPC); // NOLINT(build/unsigned)
     122          130 :     fh.element_id = dunedaq::daqdataformats::SourceID(stype_to_use, ele_num);
     123          130 :     std::unique_ptr<dunedaq::daqdataformats::Fragment> frag_ptr(
     124          130 :       new dunedaq::daqdataformats::Fragment(dummy_data, fragment_size));
     125          130 :     frag_ptr->set_header_fields(fh);
     126              : 
     127              :     // add fragment to TriggerRecord
     128          130 :     tr.add_fragment(std::move(frag_ptr));
     129              : 
     130          130 :   } // end loop over elements
     131              : 
     132           30 :   return tr;
     133           30 : }
     134              : 
     135              : struct CfgFixture
     136              : {
     137            8 :   explicit CfgFixture(std::string sessionName)
     138            8 :   {
     139            8 :     TLOG_DEBUG(4) << "Creating CfgFixture";
     140            8 :     setenv("DUNEDAQ_SESSION", sessionName.c_str(), 1);
     141            8 :     std::string oksConfig = "oksconflibs:test/config/summarytextwrite_test.data.xml";
     142            8 :     std::string appName = "TestApp";
     143            8 :     cfgMgr = std::make_shared<dunedaq::appfwk::ConfigurationManager>(oksConfig, appName, sessionName);
     144            8 :     TLOG_DEBUG(4) << "Done with CfgFixture";
     145            8 :   }
     146              : 
     147              :   const dunedaq::confmodel::DetectorConfig* get_detector_config()
     148              :   {
     149              :     return cfgMgr->get_session()->get_detector_configuration();
     150              :   }
     151              : 
     152              :   std::shared_ptr<dunedaq::appfwk::ConfigurationManager> cfgMgr;
     153              : };
     154              : 
     155              : BOOST_AUTO_TEST_SUITE(SummaryTextWrite_test)
     156              : 
     157            2 : BOOST_AUTO_TEST_CASE(NullConfiguration)
     158              : {
     159            5 :   BOOST_CHECK_THROW(make_data_store("SummaryTextDataStore", "dummy", nullptr, "dummy"),
     160              :                     dunedaq::dfmodules::FileDataStoreImplBadConfiguration);
     161            1 : }
     162              : 
     163            2 : BOOST_AUTO_TEST_CASE(CheckDerivation)
     164              : {
     165            1 :   CfgFixture cfg("test-session-3-1");
     166            1 :   auto data_writer_conf = cfg.cfgMgr->get_dal<dunedaq::appmodel::DataWriterModule>("dwm-01")->get_configuration();
     167            1 :   auto data_store_conf = data_writer_conf->get_data_store_params();
     168              : 
     169            1 :   auto data_store_ptr = make_data_store(data_store_conf->get_type(), data_store_conf->UID(), cfg.cfgMgr, "dwm-01");
     170            1 :   BOOST_REQUIRE(data_store_ptr != nullptr);
     171              : 
     172            1 :   const auto stub_data_store_ptr{ dynamic_cast<const dunedaq::dfmodules::SummaryTextDataStore*>(
     173            1 :     data_store_ptr.get()) }; // NOLINT(runtime/rtti)
     174            1 :   BOOST_REQUIRE(stub_data_store_ptr != nullptr);
     175              : 
     176            1 :   BOOST_REQUIRE_EQUAL(stub_data_store_ptr->get_derivval(), 773); // 773 should be the value in the config
     177            1 : }
     178              : 
     179            2 : BOOST_AUTO_TEST_CASE(WriteEventFiles)
     180              : {
     181            1 :   std::string file_path(std::filesystem::temp_directory_path());
     182              : 
     183            1 :   const int trigger_count = 5;
     184            1 :   const int apa_count = 3;
     185            1 :   const int link_count = 1;
     186            1 :   const int fragment_size = 10 + sizeof(dunedaq::daqdataformats::FragmentHeader);
     187              : 
     188              :   // delete any pre-existing files so that we start with a clean slate
     189            1 :   std::string delete_pattern = "stubwritetest.*\\.txt";
     190            1 :   delete_files_matching_pattern(file_path, delete_pattern);
     191              : 
     192              :   // create the DataStore
     193            1 :   CfgFixture cfg("test-session-3-1");
     194            1 :   auto data_writer_conf = cfg.cfgMgr->get_dal<dunedaq::appmodel::DataWriterModule>("dwm-01")->get_configuration();
     195            1 :   auto data_store_conf = data_writer_conf->get_data_store_params();
     196              : 
     197            1 :   auto data_store_conf_obj = data_store_conf->config_object();
     198            1 :   data_store_conf_obj.set_by_val<std::string>("directory_path", file_path);
     199            1 :   data_store_conf_obj.set_by_val<std::string>("mode", "one-event-per-file");
     200              : 
     201            1 :   auto data_store_ptr = make_data_store(data_store_conf->get_type(), data_store_conf->UID(), cfg.cfgMgr, "dwm-01");
     202              : 
     203              :   // write several events, each with several fragments
     204            6 :   for (int trigger_number = 1; trigger_number <= trigger_count; ++trigger_number)
     205            5 :     data_store_ptr->write(create_trigger_record(trigger_number, fragment_size, link_count * apa_count));
     206              : 
     207            1 :   data_store_ptr.reset();
     208              : 
     209              :   // check that the expected number of files was created
     210            1 :   std::string search_pattern = "stubwritetest.*\\.txt";
     211            1 :   std::vector<std::string> file_list = get_files_matching_pattern(file_path, search_pattern);
     212            1 :   BOOST_REQUIRE_EQUAL(file_list.size(), trigger_count);
     213              : 
     214              :   // clean up the files that were created
     215            1 :   file_list = delete_files_matching_pattern(file_path, delete_pattern);
     216            1 :   delete_files_matching_pattern(file_path, "HardwareMap.*\\.txt");
     217            1 :   BOOST_REQUIRE_EQUAL(file_list.size(), trigger_count);
     218            1 : }
     219              : 
     220            2 : BOOST_AUTO_TEST_CASE(WriteOneFile)
     221              : {
     222            1 :   std::string file_path(std::filesystem::temp_directory_path());
     223              : 
     224            1 :   const int trigger_count = 5;
     225            1 :   const int apa_count = 3;
     226            1 :   const int link_count = 1;
     227            1 :   const int fragment_size = 10 + sizeof(dunedaq::daqdataformats::FragmentHeader);
     228              : 
     229              :   // delete any pre-existing files so that we start with a clean slate
     230            1 :   std::string delete_pattern = "stubwritetest.*\\.txt";
     231            1 :   delete_files_matching_pattern(file_path, delete_pattern);
     232              : 
     233              :   // create the DataStore
     234            1 :   CfgFixture cfg("test-session-3-1");
     235            1 :   auto data_writer_conf = cfg.cfgMgr->get_dal<dunedaq::appmodel::DataWriterModule>("dwm-01")->get_configuration();
     236            1 :   auto data_store_conf = data_writer_conf->get_data_store_params();
     237              : 
     238            1 :   auto data_store_conf_obj = data_store_conf->config_object();
     239            1 :   data_store_conf_obj.set_by_val<std::string>("directory_path", file_path);
     240              : 
     241            1 :   auto data_store_ptr = make_data_store(data_store_conf->get_type(), data_store_conf->UID(), cfg.cfgMgr, "dwm-01");
     242              : 
     243              :   // write several events, each with several fragments
     244            6 :   for (int trigger_number = 1; trigger_number <= trigger_count; ++trigger_number)
     245            5 :     data_store_ptr->write(create_trigger_record(trigger_number, fragment_size, apa_count * link_count));
     246              : 
     247            1 :   data_store_ptr.reset();
     248              : 
     249              :   // check that the expected number of files was created
     250            1 :   std::string search_pattern = "stubwritetest.*\\.txt";
     251            1 :   std::vector<std::string> file_list = get_files_matching_pattern(file_path, search_pattern);
     252            1 :   BOOST_REQUIRE_EQUAL(file_list.size(), 1);
     253              : 
     254              :   // clean up the files that were created
     255            1 :   file_list = delete_files_matching_pattern(file_path, delete_pattern);
     256            1 :   delete_files_matching_pattern(file_path, "HardwareMap.*\\.txt");
     257            1 :   BOOST_REQUIRE_EQUAL(file_list.size(), 1);
     258            1 : }
     259              : 
     260            2 : BOOST_AUTO_TEST_CASE(NoDuplicateTimeSlices)
     261              : {
     262            1 :   std::string file_path(std::filesystem::temp_directory_path());
     263              : 
     264              :   // delete any pre-existing files so that we start with a clean slate
     265            1 :   std::string delete_pattern = "stubwritetest.*\\.txt";
     266            1 :   delete_files_matching_pattern(file_path, delete_pattern);
     267              : 
     268            1 :   CfgFixture cfg("test-session-3-1");
     269            1 :   auto data_writer_conf = cfg.cfgMgr->get_dal<dunedaq::appmodel::DataWriterModule>("dwm-01")->get_configuration();
     270            1 :   auto data_store_conf = data_writer_conf->get_data_store_params();
     271              : 
     272            1 :   auto data_store_conf_obj = data_store_conf->config_object();
     273            1 :   data_store_conf_obj.set_by_val<std::string>("directory_path", file_path);
     274              : 
     275            1 :   auto data_store_ptr = make_data_store(data_store_conf->get_type(), data_store_conf->UID(), cfg.cfgMgr, "dwm-01");
     276              : 
     277            1 :   dunedaq::daqdataformats::TimeSlice timeslice{ 999, 999 }; // timeslice #, run #
     278            1 :   dunedaq::daqdataformats::TimeSlice identical_timeslice{ 999, 999 };
     279              : 
     280            1 :   data_store_ptr->write(timeslice);
     281              : 
     282            1 :   std::string search_pattern = "stubwritetest.*\\.txt";
     283            1 :   std::vector<std::string> file_list = get_files_matching_pattern(file_path, search_pattern);
     284            1 :   BOOST_REQUIRE_EQUAL(file_list.size(), 1);
     285              : 
     286            1 :   BOOST_CHECK_THROW(data_store_ptr->write(identical_timeslice), dunedaq::dfmodules::IgnorableDataStoreProblem);
     287              : 
     288            1 :   delete_files_matching_pattern(file_path, delete_pattern);
     289            1 : }
     290              : 
     291            2 : BOOST_AUTO_TEST_CASE(WriteToBadOutputArea)
     292              : {
     293            1 :   std::string file_path("/this/path/does/not/exist");
     294              : 
     295            1 :   CfgFixture cfg("test-session-3-1");
     296            1 :   auto data_writer_conf = cfg.cfgMgr->get_dal<dunedaq::appmodel::DataWriterModule>("dwm-01")->get_configuration();
     297            1 :   auto data_store_conf = data_writer_conf->get_data_store_params();
     298              : 
     299            1 :   auto data_store_conf_obj = data_store_conf->config_object();
     300            1 :   data_store_conf_obj.set_by_val<std::string>("directory_path", file_path);
     301            1 :   auto data_store_ptr = make_data_store(data_store_conf->get_type(), data_store_conf->UID(), cfg.cfgMgr, "dwm-01");
     302              : 
     303            1 :   BOOST_CHECK_THROW(data_store_ptr->prepare_for_run(1, true), dunedaq::dfmodules::InvalidOutputPath);
     304            1 : }
     305              : 
     306            2 : BOOST_AUTO_TEST_CASE(EnormousMaxFileSize)
     307              : {
     308            1 :   CfgFixture cfg("test-session-3-1");
     309            1 :   auto data_writer_conf = cfg.cfgMgr->get_dal<dunedaq::appmodel::DataWriterModule>("dwm-01")->get_configuration();
     310            1 :   auto data_store_conf = data_writer_conf->get_data_store_params();
     311              : 
     312            1 :   auto data_store_conf_obj = data_store_conf->config_object();
     313            1 :   data_store_conf_obj.set_by_val<size_t>("max_file_size", std::numeric_limits<size_t>::max());
     314            1 :   auto data_store_ptr = make_data_store(data_store_conf->get_type(), data_store_conf->UID(), cfg.cfgMgr, "dwm-01");
     315            1 :   BOOST_CHECK_THROW(data_store_ptr->prepare_for_run(1, true), dunedaq::dfmodules::InsufficientDiskSpace);
     316            1 : }
     317              : 
     318            2 : BOOST_AUTO_TEST_CASE(FileSizeLimitResultsInMultipleFiles)
     319              : {
     320            1 :   std::string file_path(std::filesystem::temp_directory_path());
     321              : 
     322            1 :   const int trigger_count = 15;
     323            1 :   const int apa_count = 5;
     324            1 :   const int link_count = 1;
     325            1 :   const int fragment_size = 10000;
     326              : 
     327              :   // delete any pre-existing files so that we start with a clean slate
     328            1 :   std::string delete_pattern = "stubwritetest.*\\.txt";
     329            1 :   delete_files_matching_pattern(file_path, delete_pattern);
     330              : 
     331              :   // create the DataStore
     332            1 :   CfgFixture cfg("test-session-5-10");
     333            1 :   auto data_writer_conf = cfg.cfgMgr->get_dal<dunedaq::appmodel::DataWriterModule>("dwm-01")->get_configuration();
     334            1 :   auto data_store_conf = data_writer_conf->get_data_store_params();
     335              : 
     336            1 :   auto data_store_conf_obj = data_store_conf->config_object();
     337            1 :   data_store_conf_obj.set_by_val<std::string>("directory_path", file_path);
     338              : 
     339              :   // Tiny max file size needed since each trigger record just gets a simple line of text written out
     340            1 :   data_store_conf_obj.set_by_val<int>("max_file_size", 100);
     341              : 
     342            1 :   auto data_store_ptr = make_data_store(data_store_conf->get_type(), data_store_conf->UID(), cfg.cfgMgr, "dwm-01");
     343              : 
     344              :   // write several events, each with several fragments
     345           16 :   for (int trigger_number = 1; trigger_number <= trigger_count; ++trigger_number)
     346           15 :     data_store_ptr->write(create_trigger_record(trigger_number, fragment_size, apa_count * link_count));
     347              : 
     348            1 :   data_store_ptr.reset();
     349              : 
     350              :   // check that the expected number of files was created
     351            1 :   std::string search_pattern = "stubwritetest.*\\.txt";
     352            1 :   std::vector<std::string> file_list = get_files_matching_pattern(file_path, search_pattern);
     353              :   // With very small max_file_size (100 bytes) and minimal SUMMARYTEXT output, we should have multiple files
     354            1 :   BOOST_REQUIRE_EQUAL(file_list.size(), 15);
     355              : 
     356              :   // clean up the files that were created
     357            1 :   file_list = delete_files_matching_pattern(file_path, delete_pattern);
     358            1 :   delete_files_matching_pattern(file_path, "HardwareMap.*\\.txt");
     359            1 :   BOOST_REQUIRE_EQUAL(file_list.size(), 15);
     360            1 : }
     361              : 
     362            2 : BOOST_AUTO_TEST_CASE(SmallFileSizeLimitDataBlockListWrite)
     363              : {
     364            1 :   std::string file_path(std::filesystem::temp_directory_path());
     365              : 
     366            1 :   const int trigger_count = 5;
     367            1 :   const int apa_count = 5;
     368            1 :   const int link_count = 1;
     369            1 :   const int fragment_size = 100000;
     370              : 
     371              :   // 5 APAs times 100000 bytes per fragment gives 500,000 bytes per TR
     372              : 
     373              :   // delete any pre-existing files so that we start with a clean slate
     374            1 :   std::string delete_pattern = "stubwritetest.*\\.txt";
     375            1 :   delete_files_matching_pattern(file_path, delete_pattern);
     376              : 
     377              :   // create the DataStore
     378            1 :   CfgFixture cfg("test-session-5-1");
     379            1 :   auto data_writer_conf = cfg.cfgMgr->get_dal<dunedaq::appmodel::DataWriterModule>("dwm-01")->get_configuration();
     380            1 :   auto data_store_conf = data_writer_conf->get_data_store_params();
     381              : 
     382            1 :   auto data_store_conf_obj = data_store_conf->config_object();
     383            1 :   data_store_conf_obj.set_by_val<std::string>("directory_path", file_path);
     384            1 :   data_store_conf_obj.set_by_val<int>("max_file_size", 150000); // ~1.5 Fragment, ~0.3 TR
     385              : 
     386            1 :   auto data_store_ptr = make_data_store(data_store_conf->get_type(), data_store_conf->UID(), cfg.cfgMgr, "dwm-01");
     387              : 
     388              :   // write several events, each with several fragments
     389            6 :   for (int trigger_number = 1; trigger_number <= trigger_count; ++trigger_number)
     390            5 :     data_store_ptr->write(create_trigger_record(trigger_number, fragment_size, apa_count * link_count));
     391              : 
     392            1 :   data_store_ptr.reset();
     393              : 
     394              :   // check that the expected number of files was created
     395            1 :   std::string search_pattern = "stubwritetest.*\\.txt";
     396            1 :   std::vector<std::string> file_list = get_files_matching_pattern(file_path, search_pattern);
     397              :   // each TriggerRecord should be stored in its own file
     398            1 :   BOOST_REQUIRE_EQUAL(file_list.size(), 5);
     399              : 
     400              :   // clean up the files that were created
     401            1 :   file_list = delete_files_matching_pattern(file_path, delete_pattern);
     402            1 :   delete_files_matching_pattern(file_path, "HardwareMap.*\\.txt");
     403            1 :   BOOST_REQUIRE_EQUAL(file_list.size(), 5);
     404            1 : }
     405              : 
     406              : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1