DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
TestUtilities.hpp
Go to the documentation of this file.
1//
2// Created by Wesley Ketchum on 10/28/24.
3//
4
5#ifndef DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_TESTUTILS_TESTUTILITIES_HPP
6#define DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_TESTUTILS_TESTUTILITIES_HPP
7
11
12#include "boost/test/unit_test.hpp"
13
14#include <memory>
15#include <sstream>
16#include <set>
17#include <cmath>
18#include <iterator>
19
20
21namespace dunedaq{
22namespace datahandlinglibs{
23namespace test{
24
25//general test buffer struct that can be reused
26template <template<class> class BufferType, class TypeAdapter>
27void fill_buffer( std::shared_ptr< BufferType<TypeAdapter> >& buffer,
28 uint64_t const init_timestamp=0,
29 size_t const n_obj=10,
30 std::set<size_t> const obj_to_skip = {}){
31
32 //allocate mem in buffer
33 buffer->allocate_memory(n_obj);
34
35 //some accounting
36 size_t i=0,i_obj=0;
37 uint64_t next_timestamp=init_timestamp;
38
39 //while we want to add to the buffer
40 while(i_obj<n_obj){
41
42 //create a frame with appropriate timestamp
43 TypeAdapter frame;
44 frame.fake_timestamps(next_timestamp);
45
46 //increment to the next timestamp
47 next_timestamp += uint64_t(frame.get_num_frames()*TypeAdapter::expected_tick_difference);
48
49 //only write in buffer if not in the skip list
50 if(obj_to_skip.count(i)==0) {
51 buffer->write(std::move(frame));
52 ++i_obj;
53 }
54 ++i;
55 } // end while(obj_in_buffer<n_obj)
56}//end fill_buffer
57
58template <template<class> class BufferType, class TypeAdapter>
59void print_buffer(std::shared_ptr<BufferType<TypeAdapter>>& buffer, std::string desc=""){
60 std::stringstream ss;
61 ss << "Buffer (" << desc << "): ";
62 typename BufferType<TypeAdapter>::Iterator iter=buffer->begin();
63 while(iter!=buffer->end()){
64 ss << iter->get_timestamp() << " ";
65 ++iter;
66 }
67 BOOST_TEST_MESSAGE(ss.str());
68}//end print_buffer
69
70template <template<class> class BufferType, class TypeAdapter>
72{
73 //some testing vars
74 TypeAdapter test_element;
75 uint64_t ticks_between = TypeAdapter::expected_tick_difference*test_element.get_num_frames();
76
77 auto test_lower_bound = [&]( std::shared_ptr<BufferType<TypeAdapter>> buffer,
78 uint64_t test_ts,
79 uint32_t expected_idx,
80 bool with_errors=false){
81
82 BOOST_TEST_MESSAGE("\ttesting ts=" << test_ts);
83
84 TypeAdapter test_element; test_element.set_timestamp(test_ts);
85 typename BufferType<TypeAdapter>::Iterator expected_el = buffer->begin();
86 for(size_t i=0; i<expected_idx; ++i){
87 ++expected_el;
88 }
89
90 //get our lower_bound call
91 auto return_el = buffer->lower_bound(test_element,with_errors);
92
93 //loop through to get the previous element to return_el
94 typename BufferType<TypeAdapter>::Iterator scan_el = buffer->begin();
95 typename BufferType<TypeAdapter>::Iterator prev_el = buffer->begin();
96 while(scan_el!=return_el){
97 ++scan_el;
98 if(scan_el==return_el) break;
99 ++prev_el;
100 }
101
102 //check that expected and return timestamps agree
103 BOOST_CHECK_MESSAGE(expected_el->get_timestamp()==return_el->get_timestamp(),
104 "Expected ts{" << expected_el->get_timestamp() << "} == return ts{" << return_el->get_timestamp() << "} for test_ts=" << test_ts);
105
106 //check that we satisfy the lower bound condition
107 BOOST_CHECK_MESSAGE(return_el->get_timestamp()>=test_ts,
108 "Returned ts{" << return_el->get_timestamp() << "} is >= test_ts{" << test_ts << "}");
109 BOOST_CHECK_MESSAGE((prev_el->get_timestamp()<test_ts || return_el==buffer->begin()),
110 "Prev ts{" << prev_el->get_timestamp() << "} is < test_ts{" << test_ts << "} (or lower bound is begin of buffer)");
111 }; //end test_lower_bounds
112
113
114
115 /*
116 * Unskipped buffer should have elements with index [0, 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]
117 * and timestamps [0,1*T,2*T,3*T,4*T,5*T,6*T,7*T,8*T,9*T]
118 * where T = DTS ticks between successive elements (tick_diff_per_frame * n_frames_per_obj_in_buffer)
119 */
120 BOOST_TEST_MESSAGE("Testing buffer without skips...");
121 auto buffer_noskip = std::make_shared< BufferType<TypeAdapter> >();
122 fill_buffer<BufferType,TypeAdapter>(buffer_noskip,0,10);
123 print_buffer<BufferType,TypeAdapter>(buffer_noskip,"noskip");
124
125 // get lower bound on aligned element
126 // should return the exact value
127 test_lower_bound(buffer_noskip,ticks_between*2,2);
128
129 if(ticks_between>1) //unaligned tests don't make sense if ticks_between < 1
130 {
131 // get lower bound when maximally unaligned
132 // should get the next element up
133 test_lower_bound(buffer_noskip, ticks_between * 5 / 2, 3);
134
135 // get lower bound when minimally unaligned, next instance
136 test_lower_bound(buffer_noskip, ticks_between + 1, 2);
137 }
138
139 /*
140 * Skipped buffer should have elements with index [0, 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]
141 * and timestamps [0,1*T,4*T,5*T,6*T,7*T,8*T,9*T,10*T,11*T]
142 * where T = DTS ticks between successive elements (tick_diff_per_frame * n_frames_per_obj_in_buffer)
143 */
144 BOOST_TEST_MESSAGE("Testing buffer with skips...");
145 std::set<size_t> obj_to_skip = {2,3};
146 auto buffer_skip = std::make_shared< BufferType<TypeAdapter> >();
147 fill_buffer<BufferType,TypeAdapter>(buffer_skip,0,10,obj_to_skip);
148 print_buffer<BufferType,TypeAdapter>(buffer_skip,"skip");
149
150 // get lower bound on aligned but skipped element
151 // should return next available
152 test_lower_bound(buffer_skip,ticks_between*2,2,true);
153 test_lower_bound(buffer_skip,ticks_between*3,2,true);
154 // should be unaffected
155 test_lower_bound(buffer_skip,ticks_between,1,true);
156 test_lower_bound(buffer_skip,ticks_between*4,2,true);
157
158 if(ticks_between>1) //unaligned tests don't make sense if ticks_between < 1
159 {
160 // get lower bound when maximally unaligned
161 test_lower_bound(buffer_skip, ticks_between * 3 / 2, 2, true);
162 test_lower_bound(buffer_skip, ticks_between * 5 / 2, 2, true);
163 test_lower_bound(buffer_skip, ticks_between * 7 / 2, 2, true);
164 // should be unaffected
165 test_lower_bound(buffer_skip, ticks_between * 1 / 2, 1, true);
166 test_lower_bound(buffer_skip, ticks_between * 9 / 2, 3, true);
167 test_lower_bound(buffer_skip, ticks_between * 11 / 2, 4, true);
168
169 // get lower bound when minimally unaligned, next instance
170 test_lower_bound(buffer_skip, ticks_between + 1, 2, true);
171 test_lower_bound(buffer_skip, ticks_between * 2 + 1, 2, true);
172 // should be unaffected
173 test_lower_bound(buffer_skip, 1, 1, true);
174 }
175
176}//end test_queue_model
177
178
179template<class ReadoutType, class LatencyBufferType>
181{
182public:
183 TestDefaultRequestHandlerModel(std::shared_ptr<LatencyBufferType>& latency_buffer,
184 std::unique_ptr<dunedaq::datahandlinglibs::FrameErrorRegistry>& error_registry)
185 : dunedaq::datahandlinglibs::DefaultRequestHandlerModel<ReadoutType, LatencyBufferType>(latency_buffer,error_registry) {}
187};
188
189template <template<class> class BufferType, class TypeAdapter>
191
193
194 //some testing vars
195 TypeAdapter test_element;
196 uint64_t n_frames = test_element.get_num_frames();
197 uint64_t ticks_per_frame = TypeAdapter::expected_tick_difference;
198 uint64_t ticks_between = ticks_per_frame*n_frames;
199
200
201 // function for testing get_fragment_pieces in cases where there are no skips in the buffer
202 auto test_req_bounds = [&](std::shared_ptr<BufferType<TypeAdapter>> buffer,
203 uint64_t start_win, uint64_t end_win,
204 std::set<size_t> objects_skipped={}){
205
206 BOOST_TEST_MESSAGE("\ttesting [start_win{" << start_win << "}, end_win{" << end_win << "})");
207
208 //make the error registry we need
209 auto errorRegistry = std::make_unique<dunedaq::datahandlinglibs::FrameErrorRegistry>();
210
211 //create the request handler
212 DefaultRequestHandler requestHandler(buffer,errorRegistry);
213
214 //call the get_fragment_pieces
215 auto dfmessage = dunedaq::dfmessages::DataRequest();
216 auto req_res = typename DefaultRequestHandler::RequestResult(DefaultRequestHandler::ResultCode::kUnknown,dfmessage);
217 auto ret = requestHandler.get_fragment_pieces(start_win,end_win,req_res);
218
219 //check that the return code is correct.
220 BOOST_CHECK_EQUAL(req_res.result_code,DefaultRequestHandler::ResultCode::kFound);
221
222
223
224 //check that the first and last returned blocks are not empty
225 BOOST_REQUIRE_GT(ret.front().second,0);
226 BOOST_REQUIRE_GT(ret.back().second,0);
227
228 //grab the (first) timestamps of the first and last frames
229 uint64_t first_ts = reinterpret_cast<const TypeAdapter::FrameType*>(ret.front().first)->get_timestamp();
230
231 // if n_frames is 1, then all payloads are just one frame, and we can grab the timestamp directly
232 // this is particularly useful for not having to deal with wrapper objects, as those complicate things a bunch
233 // if not, then we really hope that sizeof(TypeAdapter::FrameType) is what we want ...
234 uint64_t last_ts=0;
235 if(n_frames==1)
236 last_ts = reinterpret_cast<const TypeAdapter::FrameType*>(ret.back().first)->get_timestamp();
237 else
238 last_ts = reinterpret_cast<const TypeAdapter::FrameType*>((char*)(ret.back().first)+(ret.back().second)-sizeof(typename TypeAdapter::FrameType))->get_timestamp();
239
240 //general check:
241 // first_ts <= start_win < first_ts + ticks_per_frame
242 // last_ts < end_win <= last_ts + ticks_per_frame
243 if(objects_skipped.size()==0)
244 BOOST_CHECK_MESSAGE(first_ts<=start_win,
245 "first_frame_ts{" << first_ts << "} <= start_win{" << start_win << "}");
246 BOOST_CHECK_MESSAGE(start_win<first_ts+ticks_per_frame,
247 "start_win{" << start_win << "} < first_frame_ts+ticks_per_frame{" << first_ts+ticks_per_frame << "}");
248 BOOST_CHECK_MESSAGE(last_ts<end_win,
249 "Check last_frame_ts{" << last_ts << "} < end_win{" << end_win << "}");
250 if(objects_skipped.size()==0)
251 BOOST_CHECK_MESSAGE(end_win<=last_ts+ticks_per_frame,
252 "end_win{" << end_win << "} <= last_frame_ts+ticks_per_frame{" << last_ts+ticks_per_frame << "}");
253
254 //expected timestamps for begin of fragment and 'end' of fragment, assuming no skipping
255 auto expected_start = TypeAdapter::expected_tick_difference *
256 (uint64_t) std::floor((float) (start_win) / (float) (ticks_per_frame));
257 auto expected_end = TypeAdapter::expected_tick_difference *
258 (uint64_t) std::ceil((float) (end_win) / (float) (ticks_per_frame));
259
260 //correct for the cases there that object has been skipped
261 auto expected_start_obj = expected_start - expected_start%ticks_between;
262 while(objects_skipped.count(expected_start_obj/ticks_between)>0 && expected_start<expected_end) {
263 expected_start += ticks_per_frame;
264 expected_start_obj = expected_start - expected_start%ticks_between;
265 }
266
267 auto expected_end_obj = expected_end - ticks_per_frame - (expected_end-ticks_per_frame)%ticks_between;
268 while(objects_skipped.count(expected_end_obj/ticks_between)>0 && expected_end>(expected_start+ticks_per_frame) ) {
269 expected_end -= ticks_per_frame;
270 expected_start_obj = expected_end - ticks_per_frame - (expected_end-ticks_per_frame)%ticks_between;
271 }
272
273 //specfic check: are values for this request what we expect
274 BOOST_CHECK_MESSAGE(first_ts == expected_start,
275 "Fragment start ts {" << first_ts << "} is expected value {" << expected_start << "}");
276 BOOST_CHECK_MESSAGE((last_ts + TypeAdapter::expected_tick_difference) == expected_end,
277 "Fragment 'end' ts {" << last_ts + TypeAdapter::expected_tick_difference
278 << "} is expected value {" << expected_end << "}");
279 };//end test_req_bounds
280
281 /*
282 * Unskipped buffer should have elements with index [0, 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]
283 * and timestamps [0,1*T,2*T,3*T,4*T,5*T,6*T,7*T,8*T,9*T]
284 * where T = DTS ticks between successive elements (tick_diff_per_frame * n_frames_per_obj_in_buffer)
285 */
286 BOOST_TEST_MESSAGE("Testing buffer without skips...");
287 auto buffer_noskip = std::make_shared< BufferType<TypeAdapter> >();
288 fill_buffer<BufferType,TypeAdapter>(buffer_noskip,0,10);
289 print_buffer<BufferType,TypeAdapter>(buffer_noskip,"noskip");
290
291 test_req_bounds(buffer_noskip,ticks_between*2,ticks_between*5);
292 test_req_bounds(buffer_noskip,ticks_between*3/2,ticks_between*9/2);
293 test_req_bounds(buffer_noskip,ticks_between*11/5,ticks_between*21/5);
294 test_req_bounds(buffer_noskip,ticks_between*2+1,ticks_between*5+1);
295
296 /*
297 * Skipped buffer should have elements with index [0, 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]
298 * and timestamps [0,1*T,4*T,5*T,6*T,7*T,8*T,9*T,10*T,11*T]
299 * where T = DTS ticks between successive elements (tick_diff_per_frame * n_frames_per_obj_in_buffer)
300 */
301 BOOST_TEST_MESSAGE("Testing buffer with skips...");
302 std::set<size_t> obj_to_skip = {2,3};
303 auto buffer_skip = std::make_shared< BufferType<TypeAdapter> >();
304 fill_buffer<BufferType,TypeAdapter>(buffer_skip,0,10,obj_to_skip);
305 print_buffer<BufferType,TypeAdapter>(buffer_skip,"skip");
306
307 test_req_bounds(buffer_skip,ticks_between*2,ticks_between*5,obj_to_skip);
308 test_req_bounds(buffer_skip,ticks_between*3/2,ticks_between*9/2,obj_to_skip);
309 if((ticks_between*21/5)>4*ticks_between) //protect in cases where ticks_between is very small (e.g. 1)
310 test_req_bounds(buffer_skip,ticks_between*11/5,ticks_between*21/5,obj_to_skip);
311 test_req_bounds(buffer_skip,ticks_between*2+1,ticks_between*5+1,obj_to_skip);
312
313}//end test_request_model
314
315}//namespace test
316}//namespace datahandlinglibs
317}//namespace dunedaq
318
319
320#endif //DATAHANDLINGLIBS_INCLUDE_DATAHANDLINGLIBS_TESTUTILS_TESTUTILITIES_HPP
std::vector< std::pair< void *, size_t > > get_fragment_pieces(uint64_t start_win_ts, uint64_t end_win_ts, RequestResult &rres)
TestDefaultRequestHandlerModel(std::shared_ptr< LatencyBufferType > &latency_buffer, std::unique_ptr< dunedaq::datahandlinglibs::FrameErrorRegistry > &error_registry)
void print_buffer(std::shared_ptr< BufferType< TypeAdapter > > &buffer, std::string desc="")
void fill_buffer(std::shared_ptr< BufferType< TypeAdapter > > &buffer, uint64_t const init_timestamp=0, size_t const n_obj=10, std::set< size_t > const obj_to_skip={})
Including Qt Headers.
This message represents a request for data sent to a single component of the DAQ.