• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paulmthompson / WhiskerToolbox / 17270491352

27 Aug 2025 02:57PM UTC coverage: 65.333%. Remained the same
17270491352

push

github

paulmthompson
Merge branch 'main' of https://github.com/paulmthompson/WhiskerToolbox

352 of 628 new or added lines in 92 files covered. (56.05%)

357 existing lines in 24 files now uncovered.

26429 of 40453 relevant lines covered (65.33%)

1119.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.04
/src/DataManager/utils/TableView/computers/IntervalOverlapComputer.test.cpp
1
#include <catch2/catch_test_macros.hpp>
2
#include <catch2/catch_approx.hpp>
3
#include <catch2/matchers/catch_matchers_floating_point.hpp>
4

5
#include "IntervalOverlapComputer.h"
6
#include "utils/TableView/core/ExecutionPlan.h"
7
#include "utils/TableView/interfaces/IIntervalSource.h"
8
#include "TimeFrame/interval_data.hpp"
9
#include "TimeFrame/TimeFrame.hpp"
10

11
// Additional includes for extended testing
12
#include "DataManager.hpp"
13
#include "utils/TableView/ComputerRegistry.hpp"
14
#include "utils/TableView/adapters/DataManagerExtension.h"
15
#include "utils/TableView/core/TableView.h"
16
#include "utils/TableView/core/TableViewBuilder.h"
17
#include "utils/TableView/interfaces/IRowSelector.h"
18
#include "utils/TableView/pipeline/TablePipeline.hpp"
19
#include "utils/TableView/TableRegistry.hpp"
20
#include "DigitalTimeSeries/Digital_Interval_Series.hpp"
21

22
#include <memory>
23
#include <vector>
24
#include <cstdint>
25
#include <numeric>
26
#include <nlohmann/json.hpp>
27

28
/**
29
 * @brief Base test fixture for IntervalOverlapComputer with realistic interval data
30
 * 
31
 * This fixture provides a DataManager populated with:
32
 * - TimeFrames with different granularities
33
 * - Row intervals representing behavior periods  
34
 * - Column intervals representing stimulus periods
35
 * - Cross-timeframe overlaps for testing timeframe conversion
36
 */
37
class IntervalOverlapTestFixture {
38
protected:
39
    IntervalOverlapTestFixture() {
9✔
40
        // Initialize the DataManager
41
        m_data_manager = std::make_unique<DataManager>();
9✔
42
        
43
        // Populate with test data
44
        populateWithIntervalTestData();
9✔
45
    }
9✔
46

47
    ~IntervalOverlapTestFixture() = default;
9✔
48

49
    /**
50
     * @brief Get the DataManager instance
51
     */
52
    DataManager & getDataManager() { return *m_data_manager; }
18✔
53
    DataManager const & getDataManager() const { return *m_data_manager; }
54
    DataManager * getDataManagerPtr() { return m_data_manager.get(); }
55

56
private:
57
    std::unique_ptr<DataManager> m_data_manager;
58

59
    /**
60
     * @brief Populate the DataManager with interval test data
61
     */
62
    void populateWithIntervalTestData() {
9✔
63
        createTimeFrames();
9✔
64
        createBehaviorIntervals();
9✔
65
        createStimulusIntervals();
9✔
66
    }
9✔
67

68
    /**
69
     * @brief Create TimeFrame objects for different data streams
70
     */
71
    void createTimeFrames() {
9✔
72
        // Create "behavior_time" timeframe: 0 to 100 (101 points) - behavior tracking at 10Hz
73
        std::vector<int> behavior_time_values(101);
27✔
74
        std::iota(behavior_time_values.begin(), behavior_time_values.end(), 0);
9✔
75
        auto behavior_time_frame = std::make_shared<TimeFrame>(behavior_time_values);
9✔
76
        m_data_manager->setTime(TimeKey("behavior_time"), behavior_time_frame, true);
9✔
77

78
        // Create "stimulus_time" timeframe: 0, 5, 10, 15, ..., 100 (21 points) - stimulus at 2Hz
79
        std::vector<int> stimulus_time_values;
9✔
80
        stimulus_time_values.reserve(21);
9✔
81
        for (int i = 0; i <= 20; ++i) {
198✔
82
            stimulus_time_values.push_back(i * 5);
189✔
83
        }
84
        auto stimulus_time_frame = std::make_shared<TimeFrame>(stimulus_time_values);
9✔
85
        m_data_manager->setTime(TimeKey("stimulus_time"), stimulus_time_frame, true);
9✔
86
    }
18✔
87

88
    /**
89
     * @brief Create behavior intervals (row intervals for testing)
90
     */
91
    void createBehaviorIntervals() {
9✔
92
        // Create behavior periods: exploration, rest, exploration
93
        auto behavior_intervals = std::make_shared<DigitalIntervalSeries>();
9✔
94
        
95
        // Exploration period 1: time 10-25
96
        behavior_intervals->addEvent(TimeFrameIndex(10), TimeFrameIndex(25));
9✔
97
        
98
        // Rest period: time 30-40  
99
        behavior_intervals->addEvent(TimeFrameIndex(30), TimeFrameIndex(40));
9✔
100
        
101
        // Exploration period 2: time 50-70
102
        behavior_intervals->addEvent(TimeFrameIndex(50), TimeFrameIndex(70));
9✔
103
        
104
        // Social interaction: time 80-95
105
        behavior_intervals->addEvent(TimeFrameIndex(80), TimeFrameIndex(95));
9✔
106

107
        m_data_manager->setData<DigitalIntervalSeries>("BehaviorPeriods", behavior_intervals, TimeKey("behavior_time"));
27✔
108
    }
18✔
109

110
    /**
111
     * @brief Create stimulus intervals (column intervals for testing)
112
     */
113
    void createStimulusIntervals() {
9✔
114
        // Create stimulus presentation periods
115
        auto stimulus_intervals = std::make_shared<DigitalIntervalSeries>();
9✔
116
        
117
        // Stimulus 1: time 5-15 (overlaps with exploration period 1)
118
        stimulus_intervals->addEvent(TimeFrameIndex(1), TimeFrameIndex(3));  // Index 1-3 = time 5-15
9✔
119
        
120
        // Stimulus 2: time 20-30 (overlaps with end of exploration 1 and start of rest)
121
        stimulus_intervals->addEvent(TimeFrameIndex(4), TimeFrameIndex(6));  // Index 4-6 = time 20-30
9✔
122
        
123
        // Stimulus 3: time 45-55 (overlaps with start of exploration period 2)
124
        stimulus_intervals->addEvent(TimeFrameIndex(9), TimeFrameIndex(11)); // Index 9-11 = time 45-55
9✔
125
        
126
        // Stimulus 4: time 85-95 (overlaps with social interaction)
127
        stimulus_intervals->addEvent(TimeFrameIndex(17), TimeFrameIndex(19)); // Index 17-19 = time 85-95
9✔
128

129
        m_data_manager->setData<DigitalIntervalSeries>("StimulusIntervals", stimulus_intervals, TimeKey("stimulus_time"));
27✔
130
    }
18✔
131
};
132

133
/**
134
 * @brief Test fixture combining IntervalOverlapTestFixture with TableRegistry and TablePipeline
135
 * 
136
 * This fixture provides everything needed to test JSON-based table pipeline execution:
137
 * - DataManager with interval test data (from IntervalOverlapTestFixture)
138
 * - TableRegistry for managing table configurations
139
 * - TablePipeline for executing JSON configurations
140
 */
141
class IntervalTableRegistryTestFixture : public IntervalOverlapTestFixture {
142
protected:
143
    IntervalTableRegistryTestFixture()
7✔
144
        : IntervalOverlapTestFixture() {
7✔
145
        // Use the DataManager's existing TableRegistry instead of creating a new one
146
        m_table_registry_ptr = getDataManager().getTableRegistry();
7✔
147

148
        // Initialize TablePipeline with the existing TableRegistry
149
        m_table_pipeline = std::make_unique<TablePipeline>(m_table_registry_ptr, &getDataManager());
7✔
150
    }
7✔
151

152
    ~IntervalTableRegistryTestFixture() = default;
7✔
153

154
    /**
155
     * @brief Get the TableRegistry instance
156
     * @return Reference to the TableRegistry
157
     */
158
    TableRegistry & getTableRegistry() { return *m_table_registry_ptr; }
6✔
159

160
    /**
161
     * @brief Get the TableRegistry instance (const version)
162
     * @return Const reference to the TableRegistry
163
     */
164
    TableRegistry const & getTableRegistry() const { return *m_table_registry_ptr; }
165

166
    /**
167
     * @brief Get a pointer to the TableRegistry
168
     * @return Raw pointer to the TableRegistry
169
     */
170
    TableRegistry * getTableRegistryPtr() { return m_table_registry_ptr; }
171

172
    /**
173
     * @brief Get the TablePipeline instance
174
     * @return Reference to the TablePipeline
175
     */
176
    TablePipeline & getTablePipeline() { return *m_table_pipeline; }
4✔
177

178
    /**
179
     * @brief Get the TablePipeline instance (const version)
180
     * @return Const reference to the TablePipeline
181
     */
182
    TablePipeline const & getTablePipeline() const { return *m_table_pipeline; }
183

184
    /**
185
     * @brief Get a pointer to the TablePipeline
186
     * @return Raw pointer to the TablePipeline
187
     */
188
    TablePipeline * getTablePipelinePtr() { return m_table_pipeline.get(); }
189

190
    /**
191
     * @brief Get the DataManagerExtension instance
192
     */
193
    std::shared_ptr<DataManagerExtension> getDataManagerExtension() { 
194
        if (!m_data_manager_extension) {
195
            m_data_manager_extension = std::make_shared<DataManagerExtension>(getDataManager());
196
        }
197
        return m_data_manager_extension; 
198
    }
199

200
private:
201
    TableRegistry * m_table_registry_ptr; // Points to DataManager's TableRegistry
202
    std::unique_ptr<TablePipeline> m_table_pipeline;
203
    std::shared_ptr<DataManagerExtension> m_data_manager_extension; // Lazy-initialized
204
};
205

206
// Mock implementation of IIntervalSource for testing
207
class MockIntervalSource : public IIntervalSource {
208
public:
209
    MockIntervalSource(std::string name, 
17✔
210
                       std::shared_ptr<TimeFrame> timeFrame,
211
                       std::vector<Interval> intervals)
212
        : m_name(std::move(name)), 
17✔
213
          m_timeFrame(std::move(timeFrame)), 
17✔
214
          m_intervals(std::move(intervals)) {}
34✔
215

216
    [[nodiscard]] auto getName() const -> std::string const & override {
×
217
        return m_name;
×
218
    }
219

220
    [[nodiscard]] auto getTimeFrame() const -> std::shared_ptr<TimeFrame> override {
11✔
221
        return m_timeFrame;
11✔
222
    }
223

224
    [[nodiscard]] auto size() const -> size_t override {
×
225
        return m_intervals.size();
×
226
    }
227

228
    auto getIntervals() -> std::vector<Interval> override {
×
229
        return m_intervals;
×
230
    }
231

232
    auto getIntervalsInRange(TimeFrameIndex start, TimeFrameIndex end, 
28✔
233
                            TimeFrame const * target_timeFrame) -> std::vector<Interval> override {
234
        std::vector<Interval> result;
28✔
235
        
236
        // Convert TimeFrameIndex to time values for comparison
237
        auto startTime = target_timeFrame->getTimeAtIndex(start);
28✔
238
        auto endTime = target_timeFrame->getTimeAtIndex(end);
28✔
239
        
240
        for (const auto& interval : m_intervals) {
100✔
241
            // Convert interval indices to time values using our timeframe
242
            auto intervalStartTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.start));
72✔
243
            auto intervalEndTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.end));
72✔
244
            
245
            // Check if intervals overlap in time
246
            if (intervalStartTime <= endTime && startTime <= intervalEndTime) {
72✔
247
                result.push_back(interval);
40✔
248
            }
249
        }
250
        
251
        return result;
28✔
252
    }
×
253

254
private:
255
    std::string m_name;
256
    std::shared_ptr<TimeFrame> m_timeFrame;
257
    std::vector<Interval> m_intervals;
258
};
259

260
TEST_CASE("DM - TV - IntervalOverlapComputer Basic Functionality", "[IntervalOverlapComputer]") {
5✔
261
    
262
    SECTION("AssignID operation - basic overlap detection") {
5✔
263
        // Create time frames
264
        std::vector<int> rowTimeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
265
        auto rowTimeFrame = std::make_shared<TimeFrame>(rowTimeValues);
1✔
266
        
267
        std::vector<int> colTimeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
268
        auto colTimeFrame = std::make_shared<TimeFrame>(colTimeValues);
1✔
269
        
270
        // Create column intervals (source intervals)
271
        std::vector<Interval> columnIntervals = {
1✔
272
            {0, 1},  // Interval 0: time 0-1
273
            {3, 5},  // Interval 1: time 3-5
274
            {7, 9}   // Interval 2: time 7-9
275
        };
3✔
276
        
277
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
278
            "TestIntervals", colTimeFrame, columnIntervals);
1✔
279
        
280
        // Create row intervals (from execution plan)
281
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
282
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(1)), // Row 0: time 0-1 (should overlap with interval 0)
283
            TimeFrameInterval(TimeFrameIndex(3), TimeFrameIndex(4)), // Row 1: time 3-4 (should overlap with interval 1)
284
            TimeFrameInterval(TimeFrameIndex(8), TimeFrameIndex(8)), // Row 2: time 8-8 (should overlap with interval 2)
285
            TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(6))  // Row 3: time 6-6 (should not overlap with any)
286
        };
3✔
287
        
288
        ExecutionPlan plan(rowIntervals, rowTimeFrame);
1✔
289
        
290
        // Create the computer
291
        IntervalOverlapComputer<int64_t> computer(intervalSource, 
1✔
292
                                                  IntervalOverlapOperation::AssignID,
293
                                                  "TestIntervals");
3✔
294
        
295
        // Compute the results
296
        auto results = computer.compute(plan);
1✔
297
        
298
        // Verify results
299
        REQUIRE(results.size() == 4);
1✔
300
        REQUIRE(results[0] == 0);  // Row 0 overlaps with interval 0
1✔
301
        REQUIRE(results[1] == 1);  // Row 1 overlaps with interval 1 (returns index of last matching interval)
1✔
302
        REQUIRE(results[2] == 2);  // Row 2 overlaps with interval 2 (returns index of last matching interval)
1✔
303
        REQUIRE(results[3] == -1); // Row 3 doesn't overlap with any interval
1✔
304
    }
6✔
305
    
306
    SECTION("CountOverlaps operation - basic overlap counting") {
5✔
307
        // Create time frames
308
        std::vector<int> rowTimeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
309
        auto rowTimeFrame = std::make_shared<TimeFrame>(rowTimeValues);
1✔
310
        
311
        std::vector<int> colTimeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
312
        auto colTimeFrame = std::make_shared<TimeFrame>(colTimeValues);
1✔
313
        
314
        // Create column intervals with some overlaps
315
        std::vector<Interval> columnIntervals = {
1✔
316
            {0, 2},  // Interval 0: time 0-2
317
            {1, 3},  // Interval 1: time 1-3 (overlaps with interval 0)
318
            {5, 7},  // Interval 2: time 5-7
319
            {6, 8}   // Interval 3: time 6-8 (overlaps with interval 2)
320
        };
3✔
321
        
322
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
323
            "TestIntervals", colTimeFrame, columnIntervals);
1✔
324
        
325
        // Create row intervals
326
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
327
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2)), // Row 0: time 0-2 (overlaps with intervals 0,1)
328
            TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(3)), // Row 1: time 1-3 (overlaps with intervals 0,1)
329
            TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(7)), // Row 2: time 6-7 (overlaps with intervals 2,3)
330
            TimeFrameInterval(TimeFrameIndex(9), TimeFrameIndex(9))  // Row 3: time 9-9 (no overlaps)
331
        };
3✔
332
        
333
        ExecutionPlan plan(rowIntervals, rowTimeFrame);
1✔
334
        
335
        // Create the computer
336
        IntervalOverlapComputer<int64_t> computer(intervalSource, 
1✔
337
                                                  IntervalOverlapOperation::CountOverlaps,
338
                                                  "TestIntervals");
3✔
339
        
340
        // Compute the results
341
        auto results = computer.compute(plan);
1✔
342
        
343
        // Verify results
344
        REQUIRE(results.size() == 4);
1✔
345
        // Note: The actual counting logic depends on the countOverlappingIntervals implementation
346
        // These tests verify the computer calls the counting function correctly
347
        REQUIRE(results[0] >= 0);  // Row 0 should have some overlaps
1✔
348
        REQUIRE(results[1] >= 0);  // Row 1 should have some overlaps
1✔
349
        REQUIRE(results[2] >= 0);  // Row 2 should have some overlaps
1✔
350
        REQUIRE(results[3] >= 0);  // Row 3 might have no overlaps
1✔
351
    }
6✔
352
    
353
    SECTION("Empty intervals handling") {
5✔
354
        // Create time frames
355
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
356
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
357
        
358
        // Create empty column intervals
359
        std::vector<Interval> columnIntervals;
1✔
360
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
361
            "EmptyIntervals", timeFrame, columnIntervals);
1✔
362
        
363
        // Create row intervals
364
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
365
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(1)),
366
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(3))
367
        };
3✔
368
        
369
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
370
        
371
        // Test AssignID operation
372
        IntervalOverlapComputer<int64_t> assignComputer(intervalSource, 
1✔
373
                                                       IntervalOverlapOperation::AssignID,
374
                                                       "EmptyIntervals");
3✔
375
        
376
        auto assignResults = assignComputer.compute(plan);
1✔
377
        
378
        REQUIRE(assignResults.size() == 2);
1✔
379
        REQUIRE(assignResults[0] == -1);  // No intervals to assign
1✔
380
        REQUIRE(assignResults[1] == -1);  // No intervals to assign
1✔
381
        
382
        // Test CountOverlaps operation
383
        IntervalOverlapComputer<int64_t> countComputer(intervalSource, 
1✔
384
                                                      IntervalOverlapOperation::CountOverlaps,
385
                                                      "EmptyIntervals");
3✔
386
        
387
        auto countResults = countComputer.compute(plan);
1✔
388
        
389
        REQUIRE(countResults.size() == 2);
1✔
390
        REQUIRE(countResults[0] == 0);  // No overlaps
1✔
391
        REQUIRE(countResults[1] == 0);  // No overlaps
1✔
392
    }
6✔
393
    
394
    SECTION("Single interval scenarios") {
5✔
395
        // Create time frames
396
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
397
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
398
        
399
        // Create single column interval
400
        std::vector<Interval> columnIntervals = {
1✔
401
            {1, 3}  // Single interval: time 1-3
402
        };
3✔
403
        
404
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
405
            "SingleInterval", timeFrame, columnIntervals);
1✔
406
        
407
        // Create various row intervals
408
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
409
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(0)), // Before interval
410
            TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(2)), // Overlaps with interval
411
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(3)), // Overlaps with interval
412
            TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(5))  // After interval
413
        };
3✔
414
        
415
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
416
        
417
        // Test AssignID operation
418
        IntervalOverlapComputer<int64_t> assignComputer(intervalSource, 
1✔
419
                                                       IntervalOverlapOperation::AssignID,
420
                                                       "SingleInterval");
3✔
421
        
422
        auto assignResults = assignComputer.compute(plan);
1✔
423
        
424
        REQUIRE(assignResults.size() == 4);
1✔
425
        REQUIRE(assignResults[0] == -1);  // No overlap
1✔
426
        REQUIRE(assignResults[1] == 0);   // Overlaps with interval 0
1✔
427
        REQUIRE(assignResults[2] == 0);   // Overlaps with interval 0
1✔
428
        REQUIRE(assignResults[3] == -1);  // No overlap
1✔
429
    }
6✔
430
    
431
    SECTION("Edge case: identical intervals") {
5✔
432
        // Create time frames
433
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
434
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
435
        
436
        // Create column intervals
437
        std::vector<Interval> columnIntervals = {
1✔
438
            {1, 3},  // Interval 0
439
            {1, 3}   // Interval 1 (identical to interval 0)
440
        };
3✔
441
        
442
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
443
            "IdenticalIntervals", timeFrame, columnIntervals);
1✔
444
        
445
        // Create row interval that matches exactly
446
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
447
            TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(3))
448
        };
3✔
449
        
450
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
451
        
452
        // Test AssignID operation (should return the last matching interval)
453
        IntervalOverlapComputer<int64_t> assignComputer(intervalSource, 
1✔
454
                                                       IntervalOverlapOperation::AssignID,
455
                                                       "IdenticalIntervals");
3✔
456
        
457
        auto assignResults = assignComputer.compute(plan);
1✔
458
        
459
        REQUIRE(assignResults.size() == 1);
1✔
460
        REQUIRE(assignResults[0] == 1);  // Should return the last matching interval (index 1)
1✔
461
    }
6✔
462
}
5✔
463

464
TEST_CASE("DM - TV - IntervalOverlapComputer Error Handling", "[IntervalOverlapComputer][Error]") {
1✔
465
    
466
    SECTION("ExecutionPlan without intervals throws exception") {
1✔
467
        // Create time frames
468
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
469
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
470
        
471
        // Create column intervals
472
        std::vector<Interval> columnIntervals = {{1, 3}};
3✔
473
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
474
            "TestIntervals", timeFrame, columnIntervals);
1✔
475
        
476
        // Create execution plan with indices instead of intervals
477
        std::vector<TimeFrameIndex> indices = {TimeFrameIndex(0), TimeFrameIndex(1)};
3✔
478
        ExecutionPlan plan(indices, timeFrame);
1✔
479
        
480
        // Create the computer
481
        IntervalOverlapComputer<int64_t> computer(intervalSource, 
1✔
482
                                                  IntervalOverlapOperation::AssignID,
483
                                                  "TestIntervals");
3✔
484
        
485
        // Should throw an exception
486
        REQUIRE_THROWS_AS(computer.compute(plan), std::runtime_error);
2✔
487
    }
2✔
488
}
1✔
489

490
TEST_CASE("DM - TV - IntervalOverlapComputer Template Types", "[IntervalOverlapComputer][Templates]") {
1✔
491
    
492
    SECTION("Test with different numeric types") {
1✔
493
        // Create time frames
494
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
495
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
496
        
497
        // Create column intervals
498
        std::vector<Interval> columnIntervals = {{1, 3}};
3✔
499
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
500
            "TestIntervals", timeFrame, columnIntervals);
1✔
501
        
502
        // Create row intervals
503
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
504
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(2))
505
        };
3✔
506
        
507
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
508
        
509
        // Test with int64_t
510
        IntervalOverlapComputer<int64_t> intComputer(intervalSource, 
1✔
511
                                                    IntervalOverlapOperation::AssignID,
512
                                                    "TestIntervals");
3✔
513
        
514
        auto intResults = intComputer.compute(plan);
1✔
515
        REQUIRE(intResults.size() == 1);
1✔
516
        REQUIRE(intResults[0] == 0);
1✔
517
        
518
        // Test with size_t for CountOverlaps
519
        IntervalOverlapComputer<size_t> sizeComputer(intervalSource, 
1✔
520
                                                    IntervalOverlapOperation::CountOverlaps,
521
                                                    "TestIntervals");
3✔
522
        
523
        auto sizeResults = sizeComputer.compute(plan);
1✔
524
        REQUIRE(sizeResults.size() == 1);
1✔
525
        REQUIRE(sizeResults[0] >= 0);
1✔
526
    }
2✔
527
}
1✔
528

529
TEST_CASE("DM - TV - IntervalOverlapComputer Dependency Tracking", "[IntervalOverlapComputer][Dependencies]") {
1✔
530
    
531
    SECTION("getSourceDependency returns correct source name") {
1✔
532
        // Create minimal setup
533
        std::vector<int> timeValues = {0, 1, 2};
3✔
534
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
535
        
536
        std::vector<Interval> columnIntervals = {{0, 1}};
3✔
537
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
538
            "TestSource", timeFrame, columnIntervals);
1✔
539
        
540
        // Create computer
541
        IntervalOverlapComputer<int64_t> computer(intervalSource, 
1✔
542
                                                  IntervalOverlapOperation::AssignID,
543
                                                  "TestSourceName");
3✔
544
        
545
        // Test source dependency
546
        REQUIRE(computer.getSourceDependency() == "TestSourceName");
1✔
547
    }
2✔
548
}
1✔
549

550
TEST_CASE("DM - TV - IntervalOverlapComputer Complex Scenarios", "[IntervalOverlapComputer][Complex]") {
1✔
551
    
552
    SECTION("Multiple overlapping intervals with different time scales") {
1✔
553
        // Create time frames with different scales
554
        std::vector<int> rowTimeValues = {0, 10, 20, 30, 40, 50};
3✔
555
        auto rowTimeFrame = std::make_shared<TimeFrame>(rowTimeValues);
1✔
556
        
557
        std::vector<int> colTimeValues = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50};
3✔
558
        auto colTimeFrame = std::make_shared<TimeFrame>(colTimeValues);
1✔
559
        
560
        // Create column intervals
561
        std::vector<Interval> columnIntervals = {
1✔
562
            {0, 2},   // Interval 0: time 0-10
563
            {1, 4},   // Interval 1: time 5-20 (overlaps with interval 0)
564
            {3, 6},   // Interval 2: time 15-30 (overlaps with interval 1)
565
            {8, 10}   // Interval 3: time 40-50
566
        };
3✔
567
        
568
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
569
            "ComplexIntervals", colTimeFrame, columnIntervals);
1✔
570
        
571
        // Create row intervals
572
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
573
            TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(1)), // Row 0: time 0-10
574
            TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(3)), // Row 1: time 20-30
575
            TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(5))  // Row 2: time 40-50
576
        };
3✔
577
        
578
        ExecutionPlan plan(rowIntervals, rowTimeFrame);
1✔
579
        
580
        // Test AssignID operation
581
        IntervalOverlapComputer<int64_t> assignComputer(intervalSource, 
1✔
582
                                                       IntervalOverlapOperation::AssignID,
583
                                                       "ComplexIntervals");
3✔
584
        
585
        auto assignResults = assignComputer.compute(plan);
1✔
586
        
587
        REQUIRE(assignResults.size() == 3);
1✔
588
        // Results depend on the specific overlap logic implementation
589
        // The test verifies the computer executes without errors
590
        
591
        // Test CountOverlaps operation
592
        IntervalOverlapComputer<int64_t> countComputer(intervalSource, 
1✔
593
                                                      IntervalOverlapOperation::CountOverlaps,
594
                                                      "ComplexIntervals");
3✔
595
        
596
        auto countResults = countComputer.compute(plan);
1✔
597
        
598
        REQUIRE(countResults.size() == 3);
1✔
599
        // All results should be non-negative
600
        for (auto result : countResults) {
4✔
601
            REQUIRE(result >= 0);
3✔
602
        }
603

604
        IntervalOverlapComputer<size_t> countComputer_size_t(intervalSource, 
1✔
605
                                                      IntervalOverlapOperation::CountOverlaps,
606
                                                      "ComplexIntervals");
3✔
607

608
        auto countResults_size_t = countComputer_size_t.compute(plan);
1✔
609

610
        REQUIRE(countResults_size_t.size() == 3);
1✔
611
        // All results should be non-negative
612
        for (auto result : countResults_size_t) {
4✔
613
            REQUIRE(result >= 0);
3✔
614
        }
615
    }
2✔
616
}
1✔
617

618
// Test the standalone utility functions
619
TEST_CASE("DM - TV - IntervalOverlapComputer Utility Functions", "[IntervalOverlapComputer][Utilities]") {
2✔
620
    
621
    SECTION("intervalsOverlap function") {
2✔
622
        TimeFrameInterval a(TimeFrameIndex(0), TimeFrameIndex(5));
1✔
623
        TimeFrameInterval b(TimeFrameIndex(3), TimeFrameIndex(8));
1✔
624
        TimeFrameInterval c(TimeFrameIndex(6), TimeFrameIndex(10));
1✔
625
        
626
        // Test overlapping intervals
627
        REQUIRE(intervalsOverlap(a, b) == true);
1✔
628
        
629
        // Test non-overlapping intervals
630
        REQUIRE(intervalsOverlap(a, c) == false);
1✔
631
        
632
        // Test identical intervals
633
        REQUIRE(intervalsOverlap(a, a) == true);
1✔
634
    }
2✔
635
    
636
    SECTION("findContainingInterval function") {
2✔
637
        TimeFrameInterval rowInterval(TimeFrameIndex(2), TimeFrameIndex(4));
1✔
638
        
639
        std::vector<Interval> columnIntervals = {
1✔
640
            {0, 1},   // Interval 0: doesn't contain row interval
641
            {1, 5},   // Interval 1: contains row interval
642
            {3, 7},   // Interval 2: overlaps but doesn't fully contain
643
            {6, 8}    // Interval 3: doesn't contain row interval
644
        };
3✔
645
        
646
        auto result = findContainingInterval(rowInterval, columnIntervals);
1✔
647
        
648
        // Should return the index of the interval that contains the row interval
649
        // The exact behavior depends on the implementation
650
        REQUIRE(result >= -1);  // -1 means no containing interval found
1✔
651
    }
3✔
652
    
653
    /*
654
    SECTION("countOverlappingIntervals function") {
655
        TimeFrameInterval rowInterval(TimeFrameIndex(2), TimeFrameIndex(4));
656
        
657
        std::vector<Interval> columnIntervals = {
658
            {0, 1},   // Interval 0: doesn't overlap
659
            {1, 3},   // Interval 1: overlaps
660
            {3, 5},   // Interval 2: overlaps
661
            {6, 8}    // Interval 3: doesn't overlap
662
        };
663
        
664
        auto result = countOverlappingIntervals(rowInterval, columnIntervals);
665
        
666
        // Should count overlapping intervals
667
        REQUIRE(result >= 0);
668
        REQUIRE(result <= static_cast<int64_t>(columnIntervals.size()));
669
    }
670
        */
671
}
2✔
672

673
TEST_CASE_METHOD(IntervalOverlapTestFixture, "DM - TV - IntervalOverlapComputer with DataManager fixture", "[IntervalOverlapComputer][DataManager][Fixture]") {
2✔
674
    
675
    SECTION("Test with behavior and stimulus intervals from fixture") {
2✔
676
        auto& dm = getDataManager();
1✔
677
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
678
        
679
        // Get the interval sources from the DataManager
680
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
681
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
682
        
683
        REQUIRE(behavior_source != nullptr);
1✔
684
        REQUIRE(stimulus_source != nullptr);
1✔
685
        
686
        // Create row selector from behavior intervals
687
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
688
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
689
            TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
690
        
691
        REQUIRE(behavior_intervals.size() == 4); // 4 behavior periods
1✔
692
        
693
        // Convert to TimeFrameIntervals for row selector
694
        std::vector<TimeFrameInterval> row_intervals;
1✔
695
        for (const auto& interval : behavior_intervals) {
5✔
696
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
697
        }
698
        
699
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
700
        
701
        // Create TableView builder
702
        TableViewBuilder builder(dme);
1✔
703
        builder.setRowSelector(std::move(row_selector));
1✔
704
        
705
        // Test AssignID operation
706
        builder.addColumn<int64_t>("Stimulus_ID", 
5✔
707
            std::make_unique<IntervalOverlapComputer<int64_t>>(stimulus_source, 
3✔
708
                IntervalOverlapOperation::AssignID, "StimulusIntervals"));
2✔
709
        
710
        // Test CountOverlaps operation
711
        builder.addColumn<int64_t>("Stimulus_Count", 
5✔
712
            std::make_unique<IntervalOverlapComputer<int64_t>>(stimulus_source, 
3✔
713
                IntervalOverlapOperation::CountOverlaps, "StimulusIntervals"));
2✔
714
        
715
        // Build the table
716
        TableView table = builder.build();
1✔
717
        
718
        // Verify table structure
719
        REQUIRE(table.getRowCount() == 4);
1✔
720
        REQUIRE(table.getColumnCount() == 2);
1✔
721
        REQUIRE(table.hasColumn("Stimulus_ID"));
3✔
722
        REQUIRE(table.hasColumn("Stimulus_Count"));
3✔
723
        
724
        // Get the column data
725
        auto stimulus_ids = table.getColumnValues<int64_t>("Stimulus_ID");
3✔
726
        auto stimulus_counts = table.getColumnValues<int64_t>("Stimulus_Count");
3✔
727
        
728
        REQUIRE(stimulus_ids.size() == 4);
1✔
729
        REQUIRE(stimulus_counts.size() == 4);
1✔
730
        
731
        // Verify the overlap results
732
        // Expected overlaps based on our test data:
733
        // Behavior 1 (10-25): overlaps with Stimulus 1 (5-15) and Stimulus 2 (20-30)
734
        // Behavior 2 (30-40): overlaps with Stimulus 2 (20-30)  
735
        // Behavior 3 (50-70): overlaps with Stimulus 3 (45-55)
736
        // Behavior 4 (80-95): overlaps with Stimulus 4 (85-95)
737
        
738
        // Note: Actual results depend on the cross-timeframe conversion implementation
739
        // These tests verify the computer executes without errors and produces reasonable results
740
        for (size_t i = 0; i < 4; ++i) {
5✔
741
            REQUIRE(stimulus_ids[i] >= -1);     // -1 means no overlap, >= 0 means valid stimulus ID
4✔
742
            REQUIRE(stimulus_counts[i] >= 0);   // Should be non-negative count
4✔
743
            REQUIRE(stimulus_counts[i] <= 4);   // Cannot exceed total number of stimuli
4✔
744
        }
745
    }
3✔
746
    
747
    SECTION("Test cross-timeframe overlap detection") {
2✔
748
        auto& dm = getDataManager();
1✔
749
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
750
        
751
        // Get sources from different timeframes
752
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");  // behavior_time frame
3✔
753
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");   // stimulus_time frame
3✔
754
        
755
        REQUIRE(behavior_source != nullptr);
1✔
756
        REQUIRE(stimulus_source != nullptr);
1✔
757
        
758
        // Verify they have different timeframes
759
        auto behavior_tf = behavior_source->getTimeFrame();
1✔
760
        auto stimulus_tf = stimulus_source->getTimeFrame();
1✔
761
        REQUIRE(behavior_tf != stimulus_tf);
1✔
762
        REQUIRE(behavior_tf->getTotalFrameCount() == 101);  // behavior_time: 0-100
1✔
763
        REQUIRE(stimulus_tf->getTotalFrameCount() == 21);   // stimulus_time: 0,5,10,...,100
1✔
764
        
765
        // Create a simple test with one behavior interval
766
        std::vector<TimeFrameInterval> test_intervals = {
1✔
767
            TimeFrameInterval(TimeFrameIndex(10), TimeFrameIndex(25))  // Behavior period 1
768
        };
3✔
769
        
770
        auto row_selector = std::make_unique<IntervalSelector>(test_intervals, behavior_tf);
1✔
771
        
772
        TableViewBuilder builder(dme);
1✔
773
        builder.setRowSelector(std::move(row_selector));
1✔
774
        
775
        // Add overlap analysis columns
776
        builder.addColumn<int64_t>("Stimulus_ID", 
5✔
777
            std::make_unique<IntervalOverlapComputer<int64_t>>(stimulus_source, 
3✔
778
                IntervalOverlapOperation::AssignID, "StimulusIntervals"));
2✔
779
        
780
        builder.addColumn<int64_t>("Stimulus_Count", 
5✔
781
            std::make_unique<IntervalOverlapComputer<int64_t>>(stimulus_source, 
3✔
782
                IntervalOverlapOperation::CountOverlaps, "StimulusIntervals"));
2✔
783
        
784
        // Build and verify the table
785
        TableView table = builder.build();
1✔
786
        
787
        REQUIRE(table.getRowCount() == 1);
1✔
788
        REQUIRE(table.getColumnCount() == 2);
1✔
789
        
790
        auto stimulus_ids = table.getColumnValues<int64_t>("Stimulus_ID");
3✔
791
        auto stimulus_counts = table.getColumnValues<int64_t>("Stimulus_Count");
3✔
792
        
793
        REQUIRE(stimulus_ids.size() == 1);
1✔
794
        REQUIRE(stimulus_counts.size() == 1);
1✔
795
        
796
        // Verify results are reasonable (exact values depend on timeframe conversion)
797
        REQUIRE(stimulus_ids[0] >= -1);
1✔
798
        REQUIRE(stimulus_counts[0] >= 0);
1✔
799
        REQUIRE(stimulus_counts[0] <= 4);
1✔
800
        
801
        std::cout << "Cross-timeframe test - Stimulus ID: " << stimulus_ids[0] 
1✔
802
                  << ", Count: " << stimulus_counts[0] << std::endl;
1✔
803
    }
3✔
804
}
2✔
805

806
TEST_CASE_METHOD(IntervalTableRegistryTestFixture, "DM - TV - IntervalOverlapComputer via ComputerRegistry", "[IntervalOverlapComputer][Registry]") {
3✔
807
    
808
    SECTION("Verify IntervalOverlapComputer is registered in ComputerRegistry") {
3✔
809
        auto& registry = getTableRegistry().getComputerRegistry();
1✔
810
        
811
        // Check that all interval overlap computers are registered
812
        auto assign_id_info = registry.findComputerInfo("Interval Overlap Assign ID");
3✔
813
        auto count_info = registry.findComputerInfo("Interval Overlap Count");
3✔
814
        auto assign_start_info = registry.findComputerInfo("Interval Overlap Assign Start");
3✔
815
        auto assign_end_info = registry.findComputerInfo("Interval Overlap Assign End");
3✔
816
        
817
        REQUIRE(assign_id_info != nullptr);
1✔
818
        REQUIRE(count_info != nullptr);
1✔
819
        REQUIRE(assign_start_info != nullptr);
1✔
820
        REQUIRE(assign_end_info != nullptr);
1✔
821
        
822
        // Verify computer info details
823
        REQUIRE(assign_id_info->name == "Interval Overlap Assign ID");
1✔
824
        REQUIRE(assign_id_info->outputType == typeid(int64_t));
1✔
825
        REQUIRE(assign_id_info->outputTypeName == "int64_t");
1✔
826
        REQUIRE(assign_id_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
827
        REQUIRE(assign_id_info->requiredSourceType == typeid(std::shared_ptr<IIntervalSource>));
1✔
828
        
829
        REQUIRE(count_info->name == "Interval Overlap Count");
1✔
830
        REQUIRE(count_info->outputType == typeid(int64_t));
1✔
831
        REQUIRE(count_info->outputTypeName == "int64_t");
1✔
832
        REQUIRE(count_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
833
        REQUIRE(count_info->requiredSourceType == typeid(std::shared_ptr<IIntervalSource>));
1✔
834
    }
3✔
835
    
836
    SECTION("Create IntervalOverlapComputer via ComputerRegistry") {
3✔
837
        auto& dm = getDataManager();
1✔
838
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
839
        auto& registry = getTableRegistry().getComputerRegistry();
1✔
840
        
841
        // Get stimulus source for testing
842
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
843
        REQUIRE(stimulus_source != nullptr);
1✔
844
        
845
        // Create computers via registry
846
        std::map<std::string, std::string> empty_params;
1✔
847
        
848
        auto assign_id_computer = registry.createTypedComputer<int64_t>(
1✔
849
            "Interval Overlap Assign ID", stimulus_source, empty_params);
3✔
850
        auto count_computer = registry.createTypedComputer<int64_t>(
1✔
851
            "Interval Overlap Count", stimulus_source, empty_params);
3✔
852
        
853
        REQUIRE(assign_id_computer != nullptr);
1✔
854
        REQUIRE(count_computer != nullptr);
1✔
855
        
856
        // Test that the created computers work correctly
857
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
858
        
859
        // Create a simple test interval
860
        std::vector<TimeFrameInterval> test_intervals = {
1✔
861
            TimeFrameInterval(TimeFrameIndex(50), TimeFrameIndex(70))  // Behavior period 3
862
        };
3✔
863
        
864
        auto row_selector = std::make_unique<IntervalSelector>(test_intervals, behavior_time_frame);
1✔
865
        
866
        TableViewBuilder builder(dme);
1✔
867
        builder.setRowSelector(std::move(row_selector));
1✔
868
        
869
        // Use the registry-created computers
870
        builder.addColumn("RegistryAssignID", std::move(assign_id_computer));
3✔
871
        builder.addColumn("RegistryCount", std::move(count_computer));
3✔
872
        
873
        // Build and verify the table
874
        TableView table = builder.build();
1✔
875
        
876
        REQUIRE(table.getRowCount() == 1);
1✔
877
        REQUIRE(table.getColumnCount() == 2);
1✔
878
        REQUIRE(table.hasColumn("RegistryAssignID"));
3✔
879
        REQUIRE(table.hasColumn("RegistryCount"));
3✔
880
        
881
        auto assign_ids = table.getColumnValues<int64_t>("RegistryAssignID");
3✔
882
        auto counts = table.getColumnValues<int64_t>("RegistryCount");
3✔
883
        
884
        REQUIRE(assign_ids.size() == 1);
1✔
885
        REQUIRE(counts.size() == 1);
1✔
886
        
887
        // Verify reasonable results
888
        REQUIRE(assign_ids[0] >= -1);
1✔
889
        REQUIRE(counts[0] >= 0);
1✔
890
        REQUIRE(counts[0] <= 4);
1✔
891
        
892
        std::cout << "Registry test - Assign ID: " << assign_ids[0] 
1✔
893
                  << ", Count: " << counts[0] << std::endl;
1✔
894
    }
4✔
895
    
896
    SECTION("Compare registry-created vs direct-created computers") {
3✔
897
        auto& dm = getDataManager();
1✔
898
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
899
        auto& registry = getTableRegistry().getComputerRegistry();
1✔
900
        
901
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
902
        REQUIRE(stimulus_source != nullptr);
1✔
903
        
904
        // Create computer via registry
905
        std::map<std::string, std::string> empty_params;
1✔
906
        auto registry_computer = registry.createTypedComputer<int64_t>(
1✔
907
            "Interval Overlap Count", stimulus_source, empty_params);
3✔
908
        
909
        // Create computer directly
910
        auto direct_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
911
            stimulus_source, IntervalOverlapOperation::CountOverlaps, "StimulusIntervals");
1✔
912
        
913
        REQUIRE(registry_computer != nullptr);
1✔
914
        REQUIRE(direct_computer != nullptr);
1✔
915
        
916
        // Test both computers with the same data
917
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
918
        std::vector<TimeFrameInterval> test_intervals = {
1✔
919
            TimeFrameInterval(TimeFrameIndex(50), TimeFrameIndex(70))
920
        };
3✔
921
        
922
        ExecutionPlan plan(test_intervals, behavior_time_frame);
1✔
923
        
924
        auto registry_result = registry_computer->compute(plan);
1✔
925
        auto direct_result = direct_computer->compute(plan);
1✔
926
        
927
        REQUIRE(registry_result.size() == 1);
1✔
928
        REQUIRE(direct_result.size() == 1);
1✔
929
        
930
        // Results should be identical
931
        REQUIRE(registry_result[0] == direct_result[0]);
1✔
932
        
933
        std::cout << "Comparison test - Registry result: " << registry_result[0] 
1✔
934
                  << ", Direct result: " << direct_result[0] << std::endl;
1✔
935
    }
4✔
936
}
3✔
937

938
TEST_CASE_METHOD(IntervalTableRegistryTestFixture, "DM - TV - IntervalOverlapComputer via JSON TablePipeline", "[IntervalOverlapComputer][JSON][Pipeline]") {
2✔
939
    
940
    SECTION("Test CountOverlaps operation via JSON pipeline") {
2✔
941
        // JSON configuration for testing IntervalOverlapComputer through TablePipeline
942
        char const * json_config = R"({
1✔
943
            "metadata": {
944
                "name": "Interval Overlap Test",
945
                "description": "Test JSON execution of IntervalOverlapComputer",
946
                "version": "1.0"
947
            },
948
            "tables": [
949
                {
950
                    "table_id": "interval_overlap_test",
951
                    "name": "Interval Overlap Test Table",
952
                    "description": "Test table using IntervalOverlapComputer",
953
                    "row_selector": {
954
                        "type": "interval",
955
                        "source": "BehaviorPeriods"
956
                    },
957
                    "columns": [
958
                        {
959
                            "name": "StimulusOverlapCount",
960
                            "description": "Count of stimulus events overlapping with each behavior period",
961
                            "data_source": "StimulusIntervals",
962
                            "computer": "Interval Overlap Count"
963
                        },
964
                        {
965
                            "name": "StimulusOverlapID",
966
                            "description": "ID of stimulus event overlapping with each behavior period",
967
                            "data_source": "StimulusIntervals",
968
                            "computer": "Interval Overlap Assign ID"
969
                        }
970
                    ]
971
                }
972
            ]
973
        })";
974

975
        auto& pipeline = getTablePipeline();
1✔
976

977
        // Parse the JSON configuration
978
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
979

980
        // Load configuration into pipeline
981
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
982
        REQUIRE(load_success);
1✔
983

984
        // Verify configuration was loaded correctly
985
        auto table_configs = pipeline.getTableConfigurations();
1✔
986
        REQUIRE(table_configs.size() == 1);
1✔
987

988
        auto const& config = table_configs[0];
1✔
989
        REQUIRE(config.table_id == "interval_overlap_test");
1✔
990
        REQUIRE(config.name == "Interval Overlap Test Table");
1✔
991
        REQUIRE(config.columns.size() == 2);
1✔
992

993
        // Verify column configurations
994
        auto const& column1 = config.columns[0];
1✔
995
        REQUIRE(column1["name"] == "StimulusOverlapCount");
1✔
996
        REQUIRE(column1["computer"] == "Interval Overlap Count");
1✔
997
        REQUIRE(column1["data_source"] == "StimulusIntervals");
1✔
998

999
        auto const& column2 = config.columns[1];
1✔
1000
        REQUIRE(column2["name"] == "StimulusOverlapID");
1✔
1001
        REQUIRE(column2["computer"] == "Interval Overlap Assign ID");
1✔
1002
        REQUIRE(column2["data_source"] == "StimulusIntervals");
1✔
1003

1004
        // Verify row selector configuration
1005
        REQUIRE(config.row_selector["type"] == "interval");
1✔
1006
        REQUIRE(config.row_selector["source"] == "BehaviorPeriods");
1✔
1007

1008
        std::cout << "JSON pipeline configuration loaded and parsed successfully" << std::endl;
1✔
1009

1010
        // Execute the pipeline (note: this may not fully work if interval row selector is not implemented)
1011
        auto pipeline_result = pipeline.execute([](int table_index, std::string const& table_name, int table_progress, int overall_progress) {
2✔
1012
            std::cout << "Building table " << table_index << " (" << table_name << "): "
4✔
1013
                      << table_progress << "% (Overall: " << overall_progress << "%)" << std::endl;
4✔
1014
        });
2✔
1015

1016
        if (pipeline_result.success) {
1✔
1017
            std::cout << "Pipeline executed successfully!" << std::endl;
1✔
1018
            std::cout << "Tables completed: " << pipeline_result.tables_completed << "/" << pipeline_result.total_tables << std::endl;
1✔
1019
            std::cout << "Execution time: " << pipeline_result.total_execution_time_ms << " ms" << std::endl;
1✔
1020

1021
            // Verify the built table exists
1022
            auto& registry = getTableRegistry();
1✔
1023
            REQUIRE(registry.hasTable("interval_overlap_test"));
3✔
1024

1025
            // Get the built table and verify its structure
1026
            auto built_table = registry.getBuiltTable("interval_overlap_test");
3✔
1027
            REQUIRE(built_table != nullptr);
1✔
1028

1029
            // Check that the table has the expected columns
1030
            auto column_names = built_table->getColumnNames();
1✔
1031
            std::cout << "Built table has " << column_names.size() << " columns" << std::endl;
1✔
1032
            for (auto const& name : column_names) {
3✔
1033
                std::cout << "  Column: " << name << std::endl;
2✔
1034
            }
1035

1036
            REQUIRE(column_names.size() == 2);
1✔
1037
            REQUIRE(built_table->hasColumn("StimulusOverlapCount"));
3✔
1038
            REQUIRE(built_table->hasColumn("StimulusOverlapID"));
3✔
1039

1040
            // Verify table has 4 rows (one for each behavior period)
1041
            REQUIRE(built_table->getRowCount() == 4);
1✔
1042

1043
            // Get and verify the computed values
1044
            auto overlap_counts = built_table->getColumnValues<int64_t>("StimulusOverlapCount");
3✔
1045
            auto overlap_ids = built_table->getColumnValues<int64_t>("StimulusOverlapID");
3✔
1046

1047
            REQUIRE(overlap_counts.size() == 4);
1✔
1048
            REQUIRE(overlap_ids.size() == 4);
1✔
1049

1050
            for (size_t i = 0; i < 4; ++i) {
5✔
1051
                REQUIRE(overlap_counts[i] >= 0);
4✔
1052
                REQUIRE(overlap_counts[i] <= 4);  // Cannot exceed total number of stimuli
4✔
1053
                REQUIRE(overlap_ids[i] >= -1);    // -1 means no overlap, >= 0 means valid ID
4✔
1054
                
1055
                std::cout << "Row " << i << ": Count=" << overlap_counts[i] 
4✔
1056
                          << ", ID=" << overlap_ids[i] << std::endl;
4✔
1057
            }
1058

1059
        } else {
1✔
UNCOV
1060
            std::cout << "Pipeline execution failed: " << pipeline_result.error_message << std::endl;
×
1061
            // Now that we have proper fixtures and interval row selector implementation, this should succeed
1062
            FAIL("Pipeline execution failed: " + pipeline_result.error_message);
×
1063
        }
1064
    }
3✔
1065
    
1066
    SECTION("Test AssignID_Start and AssignID_End operations via JSON") {
2✔
1067
        char const * json_config = R"({
1✔
1068
            "metadata": {
1069
                "name": "Interval Overlap Start/End Test",
1070
                "description": "Test JSON execution of IntervalOverlapComputer start/end operations"
1071
            },
1072
            "tables": [
1073
                {
1074
                    "table_id": "interval_overlap_start_end_test",
1075
                    "name": "Interval Overlap Start/End Test Table",
1076
                    "description": "Test table using IntervalOverlapComputer start/end operations",
1077
                    "row_selector": {
1078
                        "type": "interval",
1079
                        "source": "BehaviorPeriods"
1080
                    },
1081
                    "columns": [
1082
                        {
1083
                            "name": "StimulusStartIndex",
1084
                            "description": "Start index of overlapping stimulus",
1085
                            "data_source": "StimulusIntervals",
1086
                            "computer": "Interval Overlap Assign Start"
1087
                        },
1088
                        {
1089
                            "name": "StimulusEndIndex",
1090
                            "description": "End index of overlapping stimulus",
1091
                            "data_source": "StimulusIntervals",
1092
                            "computer": "Interval Overlap Assign End"
1093
                        }
1094
                    ]
1095
                }
1096
            ]
1097
        })";
1098

1099
        auto& pipeline = getTablePipeline();
1✔
1100
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1101
        
1102
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1103
        REQUIRE(load_success);
1✔
1104

1105
        auto table_configs = pipeline.getTableConfigurations();
1✔
1106
        REQUIRE(table_configs.size() == 1);
1✔
1107
        
1108
        auto const& config = table_configs[0];
1✔
1109
        REQUIRE(config.columns.size() == 2);
1✔
1110
        REQUIRE(config.columns[0]["computer"] == "Interval Overlap Assign Start");
1✔
1111
        REQUIRE(config.columns[1]["computer"] == "Interval Overlap Assign End");
1✔
1112

1113
        std::cout << "Start/End operations JSON configuration parsed successfully" << std::endl;
1✔
1114
        
1115
        // Note: Full execution testing would depend on interval row selector implementation
1116
        // For now, we verify that the configuration is correctly parsed
1117
    }
3✔
1118
}
2✔
1119

1120
TEST_CASE_METHOD(IntervalTableRegistryTestFixture, "DM - TV - IntervalRowSelector implementation test", "[IntervalRowSelector][Pipeline]") {
2✔
1121
    
1122
    SECTION("Test interval row selector creation from source") {
2✔
1123
        // Simple JSON configuration to test interval row selector creation
1124
        char const * json_config = R"({
1✔
1125
            "metadata": {
1126
                "name": "Interval Row Selector Test",
1127
                "description": "Test interval row selector creation"
1128
            },
1129
            "tables": [
1130
                {
1131
                    "table_id": "interval_row_test",
1132
                    "name": "Interval Row Test Table",
1133
                    "description": "Test table with interval row selector",
1134
                    "row_selector": {
1135
                        "type": "interval",
1136
                        "source": "BehaviorPeriods"
1137
                    },
1138
                    "columns": [
1139
                        {
1140
                            "name": "StimulusCount",
1141
                            "description": "Count of overlapping stimuli",
1142
                            "data_source": "StimulusIntervals",
1143
                            "computer": "Interval Overlap Count"
1144
                        }
1145
                    ]
1146
                }
1147
            ]
1148
        })";
1149

1150
        auto& pipeline = getTablePipeline();
1✔
1151
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1152
        
1153
        // Load configuration
1154
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1155
        REQUIRE(load_success);
1✔
1156

1157
        // Execute the pipeline
1158
        auto pipeline_result = pipeline.execute();
1✔
1159
        
1160
        // Verify that the pipeline now executes successfully with our interval row selector implementation
1161
        if (pipeline_result.success) {
1✔
1162
            std::cout << "✓ Interval row selector pipeline executed successfully!" << std::endl;
1✔
1163
            
1164
            // Verify the built table exists and has correct structure
1165
            auto& registry = getTableRegistry();
1✔
1166
            REQUIRE(registry.hasTable("interval_row_test"));
3✔
1167
            
1168
            auto built_table = registry.getBuiltTable("interval_row_test");
3✔
1169
            REQUIRE(built_table != nullptr);
1✔
1170
            
1171
            // Should have 4 rows (one for each behavior period from our fixture)
1172
            REQUIRE(built_table->getRowCount() == 4);
1✔
1173
            REQUIRE(built_table->getColumnCount() == 1);
1✔
1174
            REQUIRE(built_table->hasColumn("StimulusCount"));
3✔
1175
            
1176
            auto counts = built_table->getColumnValues<int64_t>("StimulusCount");
3✔
1177
            REQUIRE(counts.size() == 4);
1✔
1178
            
1179
            // Verify all counts are reasonable
1180
            for (size_t i = 0; i < 4; ++i) {
5✔
1181
                REQUIRE(counts[i] >= 0);
4✔
1182
                REQUIRE(counts[i] <= 4);  // Cannot exceed total number of stimuli
4✔
1183
                std::cout << "Behavior period " << i << ": " << counts[i] << " overlapping stimuli" << std::endl;
4✔
1184
            }
1185
            
1186
        } else {
1✔
UNCOV
1187
            FAIL("Pipeline execution failed: " + pipeline_result.error_message);
×
1188
        }
1189
    }
3✔
1190
    
1191
    SECTION("Test interval row selector with multiple operations") {
2✔
1192
        // More comprehensive test with multiple computers
1193
        char const * json_config = R"({
1✔
1194
            "metadata": {
1195
                "name": "Multi-Operation Interval Test",
1196
                "description": "Test multiple interval overlap operations"
1197
            },
1198
            "tables": [
1199
                {
1200
                    "table_id": "multi_interval_test",
1201
                    "name": "Multi Interval Test Table",
1202
                    "description": "Test table with multiple interval overlap operations",
1203
                    "row_selector": {
1204
                        "type": "interval",
1205
                        "source": "BehaviorPeriods"
1206
                    },
1207
                    "columns": [
1208
                        {
1209
                            "name": "OverlapCount",
1210
                            "description": "Count of overlapping stimuli",
1211
                            "data_source": "StimulusIntervals",
1212
                            "computer": "Interval Overlap Count"
1213
                        },
1214
                        {
1215
                            "name": "OverlapID",
1216
                            "description": "ID of overlapping stimulus",
1217
                            "data_source": "StimulusIntervals",
1218
                            "computer": "Interval Overlap Assign ID"
1219
                        },
1220
                        {
1221
                            "name": "OverlapStart",
1222
                            "description": "Start index of overlapping stimulus",
1223
                            "data_source": "StimulusIntervals",
1224
                            "computer": "Interval Overlap Assign Start"
1225
                        },
1226
                        {
1227
                            "name": "OverlapEnd",
1228
                            "description": "End index of overlapping stimulus",
1229
                            "data_source": "StimulusIntervals",
1230
                            "computer": "Interval Overlap Assign End"
1231
                        }
1232
                    ]
1233
                }
1234
            ]
1235
        })";
1236

1237
        auto& pipeline = getTablePipeline();
1✔
1238
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1239
        
1240
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1241
        REQUIRE(load_success);
1✔
1242

1243
        auto pipeline_result = pipeline.execute();
1✔
1244
        
1245
        if (pipeline_result.success) {
1✔
1246
            std::cout << "✓ Multi-operation interval pipeline executed successfully!" << std::endl;
1✔
1247
            
1248
            auto& registry = getTableRegistry();
1✔
1249
            auto built_table = registry.getBuiltTable("multi_interval_test");
3✔
1250
            REQUIRE(built_table != nullptr);
1✔
1251
            
1252
            REQUIRE(built_table->getRowCount() == 4);
1✔
1253
            REQUIRE(built_table->getColumnCount() == 4);
1✔
1254
            
1255
            // Verify all expected columns exist
1256
            REQUIRE(built_table->hasColumn("OverlapCount"));
3✔
1257
            REQUIRE(built_table->hasColumn("OverlapID"));
3✔
1258
            REQUIRE(built_table->hasColumn("OverlapStart"));
3✔
1259
            REQUIRE(built_table->hasColumn("OverlapEnd"));
3✔
1260
            
1261
            // Get all column data
1262
            auto counts = built_table->getColumnValues<int64_t>("OverlapCount");
3✔
1263
            auto ids = built_table->getColumnValues<int64_t>("OverlapID");
3✔
1264
            auto starts = built_table->getColumnValues<int64_t>("OverlapStart");
3✔
1265
            auto ends = built_table->getColumnValues<int64_t>("OverlapEnd");
3✔
1266
            
1267
            // Verify data consistency
1268
            for (size_t i = 0; i < 4; ++i) {
5✔
1269
                REQUIRE(counts[i] >= 0);
4✔
1270
                REQUIRE(ids[i] >= -1);     // -1 means no overlap
4✔
1271
                REQUIRE(starts[i] >= -1);  // -1 means no overlap
4✔
1272
                REQUIRE(ends[i] >= -1);    // -1 means no overlap
4✔
1273
                
1274
                std::cout << "Row " << i << ": Count=" << counts[i] 
4✔
1275
                          << ", ID=" << ids[i] 
4✔
1276
                          << ", Start=" << starts[i] 
4✔
1277
                          << ", End=" << ends[i] << std::endl;
4✔
1278
            }
1279
            
1280
        } else {
1✔
UNCOV
1281
            FAIL("Multi-operation pipeline execution failed: " + pipeline_result.error_message);
×
1282
        }
1283
    }
3✔
1284
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc