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

paulmthompson / WhiskerToolbox / 17846711083

19 Sep 2025 02:28AM UTC coverage: 72.02% (+0.08%) from 71.942%
17846711083

push

github

paulmthompson
event in interval computer works with entity ids

259 of 280 new or added lines in 6 files covered. (92.5%)

268 existing lines in 17 files now uncovered.

40247 of 55883 relevant lines covered (72.02%)

1227.29 hits per line

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

97.06
/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 <algorithm>
27
#include <nlohmann/json.hpp>
28

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

48
    ~IntervalOverlapTestFixture() = default;
13✔
49

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

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

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

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

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

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

108
        m_data_manager->setData<DigitalIntervalSeries>("BehaviorPeriods", behavior_intervals, TimeKey("behavior_time"));
39✔
109
    }
26✔
110

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

130
        m_data_manager->setData<DigitalIntervalSeries>("StimulusIntervals", stimulus_intervals, TimeKey("stimulus_time"));
39✔
131
    }
26✔
132
};
133

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

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

153
    ~IntervalTableRegistryTestFixture() = default;
11✔
154

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1244
        auto pipeline_result = pipeline.execute();
1✔
1245
        
1246
        if (pipeline_result.success) {
1✔
1247
            std::cout << "✓ Multi-operation interval pipeline executed successfully!" << std::endl;
1✔
1248
            
1249
            auto& registry = getTableRegistry();
1✔
1250
            auto built_table = registry.getBuiltTable("multi_interval_test");
3✔
1251
            REQUIRE(built_table != nullptr);
1✔
1252
            
1253
            REQUIRE(built_table->getRowCount() == 4);
1✔
1254
            REQUIRE(built_table->getColumnCount() == 4);
1✔
1255
            
1256
            // Verify all expected columns exist
1257
            REQUIRE(built_table->hasColumn("OverlapCount"));
3✔
1258
            REQUIRE(built_table->hasColumn("OverlapID"));
3✔
1259
            REQUIRE(built_table->hasColumn("OverlapStart"));
3✔
1260
            REQUIRE(built_table->hasColumn("OverlapEnd"));
3✔
1261
            
1262
            // Get all column data
1263
            auto counts = built_table->getColumnValues<int64_t>("OverlapCount");
3✔
1264
            auto ids = built_table->getColumnValues<int64_t>("OverlapID");
3✔
1265
            auto starts = built_table->getColumnValues<int64_t>("OverlapStart");
3✔
1266
            auto ends = built_table->getColumnValues<int64_t>("OverlapEnd");
3✔
1267
            
1268
            // Verify data consistency
1269
            for (size_t i = 0; i < 4; ++i) {
5✔
1270
                REQUIRE(counts[i] >= 0);
4✔
1271
                REQUIRE(ids[i] >= -1);     // -1 means no overlap
4✔
1272
                REQUIRE(starts[i] >= -1);  // -1 means no overlap
4✔
1273
                REQUIRE(ends[i] >= -1);    // -1 means no overlap
4✔
1274
                
1275
                std::cout << "Row " << i << ": Count=" << counts[i] 
4✔
1276
                          << ", ID=" << ids[i] 
4✔
1277
                          << ", Start=" << starts[i] 
4✔
1278
                          << ", End=" << ends[i] << std::endl;
4✔
1279
            }
1280
            
1281
        } else {
1✔
1282
            FAIL("Multi-operation pipeline execution failed: " + pipeline_result.error_message);
×
1283
        }
1284
    }
3✔
1285
}
2✔
1286

1287
TEST_CASE_METHOD(IntervalTableRegistryTestFixture, "DM - TV - IntervalOverlapComputer EntityID Round Trip", "[IntervalOverlapComputer][EntityID][TableView]") {
4✔
1288
    
1289
    SECTION("Test Simple EntityID structure for AssignID operations") {
4✔
1290
        auto& dm = getDataManager();
1✔
1291
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1292
        
1293
        // Get the interval sources from the DataManager
1294
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1295
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
1296
        
1297
        REQUIRE(behavior_source != nullptr);
1✔
1298
        REQUIRE(stimulus_source != nullptr);
1✔
1299
        
1300
        // Create row selector from behavior intervals
1301
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1302
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
1303
            TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
1304
        
1305
        REQUIRE(behavior_intervals.size() == 4); // 4 behavior periods
1✔
1306
        
1307
        // Convert to TimeFrameIntervals for row selector
1308
        std::vector<TimeFrameInterval> row_intervals;
1✔
1309
        for (const auto& interval : behavior_intervals) {
5✔
1310
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
1311
        }
1312
        
1313
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1314
        
1315
        // Create IntervalOverlapComputer for AssignID operation (Simple EntityID structure)
1316
        auto assign_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
1317
            stimulus_source, 
1318
            IntervalOverlapOperation::AssignID, 
1✔
1319
            "StimulusIntervals"
1320
        );
1✔
1321
        
1322
        // Verify EntityID structure is Simple for AssignID operations
1323
        REQUIRE(assign_computer->getEntityIdStructure() == EntityIdStructure::Simple);
1✔
1324
        REQUIRE(assign_computer->hasEntityIds());
1✔
1325
        
1326
        // Create TableView builder and add the column
1327
        TableViewBuilder builder(dme);
1✔
1328
        builder.setRowSelector(std::move(row_selector));
1✔
1329
        builder.addColumn<int64_t>("StimulusAssignID", std::move(assign_computer));
3✔
1330
        
1331
        // Build the table
1332
        TableView table = builder.build();
1✔
1333
        
1334
        // Verify table structure
1335
        REQUIRE(table.getRowCount() == 4);
1✔
1336
        REQUIRE(table.getColumnCount() == 1);
1✔
1337
        REQUIRE(table.hasColumn("StimulusAssignID"));
3✔
1338
        
1339
        
1340
        // Test column-level EntityIDs using variant interface
1341
        ColumnEntityIds column_entity_ids = table.getColumnEntityIds("StimulusAssignID");
3✔
1342
        REQUIRE(std::holds_alternative<std::vector<EntityId>>(column_entity_ids));
1✔
1343
        
1344
        auto simple_entity_ids = std::get<std::vector<EntityId>>(column_entity_ids);
1✔
1345
        REQUIRE(simple_entity_ids.size() == 4); // One EntityID per row
1✔
1346
        
1347
        // Test cell-level EntityID extraction
1348
        for (size_t row = 0; row < 4; ++row) {
5✔
1349
            std::vector<EntityId> cell_entity_ids = table.getCellEntityIds("StimulusAssignID", row);
12✔
1350
            REQUIRE(cell_entity_ids.size() == 1); // AssignID gives single EntityID per cell
4✔
1351
            REQUIRE(cell_entity_ids[0] == simple_entity_ids[row]);
4✔
1352
        }
4✔
1353
        
1354
        std::cout << "✓ Simple EntityID structure test passed for AssignID operations" << std::endl;
1✔
1355
        std::cout << "  - Column EntityIDs: " << simple_entity_ids.size() << " entries" << std::endl;
1✔
1356
        std::cout << "  - All EntityIDs are valid and non-empty" << std::endl;
1✔
1357
    }
5✔
1358
    
1359
    SECTION("Test Complex EntityID structure for CountOverlaps operations") {
4✔
1360
        auto& dm = getDataManager();
1✔
1361
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1362
        
1363
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1364
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
1365
        
1366
        REQUIRE(behavior_source != nullptr);
1✔
1367
        REQUIRE(stimulus_source != nullptr);
1✔
1368
        
1369
        // Create row selector from behavior intervals
1370
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1371
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
1372
            TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
1373
        
1374
        std::vector<TimeFrameInterval> row_intervals;
1✔
1375
        for (const auto& interval : behavior_intervals) {
5✔
1376
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
1377
        }
1378
        
1379
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1380
        
1381
        // Create IntervalOverlapComputer for CountOverlaps operation (Complex EntityID structure)
1382
        auto count_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
1383
            stimulus_source, 
1384
            IntervalOverlapOperation::CountOverlaps, 
1✔
1385
            "StimulusIntervals"
1386
        );
1✔
1387
        
1388
        // Verify EntityID structure is Complex for CountOverlaps operations
1389
        REQUIRE(count_computer->getEntityIdStructure() == EntityIdStructure::Complex);
1✔
1390
        REQUIRE(count_computer->hasEntityIds());
1✔
1391
        
1392
        // Create TableView builder and add the column
1393
        TableViewBuilder builder(dme);
1✔
1394
        builder.setRowSelector(std::move(row_selector));
1✔
1395
        builder.addColumn<int64_t>("StimulusCount", std::move(count_computer));
3✔
1396
        
1397
        // Build the table
1398
        TableView table = builder.build();
1✔
1399
        
1400
        // Verify table structure
1401
        REQUIRE(table.getRowCount() == 4);
1✔
1402
        REQUIRE(table.getColumnCount() == 1);
1✔
1403
        REQUIRE(table.hasColumn("StimulusCount"));
3✔
1404
        
1405
        
1406
        
1407
        // Test column-level EntityIDs using variant interface
1408
        ColumnEntityIds column_entity_ids = table.getColumnEntityIds("StimulusCount");
3✔
1409
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(column_entity_ids));
1✔
1410
        
1411
        auto complex_entity_ids = std::get<std::vector<std::vector<EntityId>>>(column_entity_ids);
1✔
1412
        REQUIRE(complex_entity_ids.size() == 4); // One vector of EntityIDs per row
1✔
1413
        
1414
        // Test cell-level EntityID extraction
1415
        for (size_t row = 0; row < 4; ++row) {
5✔
1416
            std::vector<EntityId> cell_entity_ids = table.getCellEntityIds("StimulusCount", row);
12✔
1417
            REQUIRE(cell_entity_ids == complex_entity_ids[row]);
4✔
1418
            
1419
            // Each row should have zero or more EntityIDs (depending on overlaps)
1420
            REQUIRE(cell_entity_ids.size() >= 0);
4✔
1421
            
1422
        }
4✔
1423
        
1424
        std::cout << "✓ Complex EntityID structure test passed for CountOverlaps operations" << std::endl;
1✔
1425
        std::cout << "  - Column EntityIDs: " << complex_entity_ids.size() << " rows" << std::endl;
1✔
1426
        for (size_t i = 0; i < complex_entity_ids.size(); ++i) {
5✔
1427
            std::cout << "    Row " << i << ": " << complex_entity_ids[i].size() << " EntityIDs" << std::endl;
4✔
1428
        }
1429
    }
5✔
1430
    
1431
    SECTION("Test EntityID variant interface consistency") {
4✔
1432
        auto& dm = getDataManager();
1✔
1433
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1434
        
1435
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1436
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
1437
        
1438
        // Create row selector from behavior intervals
1439
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1440
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
1441
            TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
1442
        
1443
        std::vector<TimeFrameInterval> row_intervals;
1✔
1444
        for (const auto& interval : behavior_intervals) {
5✔
1445
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
1446
        }
1447
        
1448
        auto row_selector1 = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1449
        auto row_selector2 = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1450
        
1451
        // Create both types of computers
1452
        auto assign_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
1453
            stimulus_source, IntervalOverlapOperation::AssignID, "StimulusIntervals");
1✔
1454
        auto count_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
1455
            stimulus_source, IntervalOverlapOperation::CountOverlaps, "StimulusIntervals");
1✔
1456
        
1457
        // Create two separate tables to test both
1458
        TableViewBuilder builder1(dme);
1✔
1459
        builder1.setRowSelector(std::move(row_selector1));
1✔
1460
        builder1.addColumn<int64_t>("AssignColumn", std::move(assign_computer));
3✔
1461
        
1462
        TableViewBuilder builder2(dme);
1✔
1463
        builder2.setRowSelector(std::move(row_selector2));
1✔
1464
        builder2.addColumn<int64_t>("CountColumn", std::move(count_computer));
3✔
1465

1466
        TableView assign_table = builder1.build();
1✔
1467
        TableView count_table = builder2.build();
1✔
1468
        
1469
        // Test variant types
1470
        ColumnEntityIds assign_ids = assign_table.getColumnEntityIds("AssignColumn");
3✔
1471
        ColumnEntityIds count_ids = count_table.getColumnEntityIds("CountColumn");
3✔
1472

1473
        REQUIRE(std::holds_alternative<std::vector<EntityId>>(assign_ids));
1✔
1474
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(count_ids));
1✔
1475
        
1476
        // Verify both variants work correctly
1477
        auto simple_ids = std::get<std::vector<EntityId>>(assign_ids);
1✔
1478
        auto complex_ids = std::get<std::vector<std::vector<EntityId>>>(count_ids);
1✔
1479
        
1480
        REQUIRE(simple_ids.size() == 4);
1✔
1481
        REQUIRE(complex_ids.size() == 4);
1✔
1482
        
1483
        // Test cell-level consistency
1484
        for (size_t row = 0; row < 4; ++row) {
5✔
1485
            auto assign_cell = assign_table.getCellEntityIds("AssignColumn", row);
12✔
1486
            auto count_cell = count_table.getCellEntityIds("CountColumn", row);
12✔
1487

1488
            // AssignID should return single EntityID
1489
            REQUIRE(assign_cell.size() == 1);
4✔
1490
            REQUIRE(assign_cell[0] == simple_ids[row]);
4✔
1491
            
1492
            // CountOverlaps should return multiple EntityIDs (may be same as assign but in vector)
1493
            REQUIRE(count_cell == complex_ids[row]);
4✔
1494
            
1495
   
1496
        }
4✔
1497
        
1498
        std::cout << "✓ Variant interface consistency test passed" << std::endl;
1✔
1499
        std::cout << "  - Simple structure: " << simple_ids.size() << " EntityIDs" << std::endl;
1✔
1500
        std::cout << "  - Complex structure: " << complex_ids.size() << " rows of EntityIDs" << std::endl;
1✔
1501
    }
5✔
1502
    
1503
    SECTION("Test EntityID round trip with source data verification") {
4✔
1504
        auto& dm = getDataManager();
1✔
1505
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1506
        
1507
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1508
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
1509
        
1510
        REQUIRE(behavior_source != nullptr);
1✔
1511
        REQUIRE(stimulus_source != nullptr);
1✔
1512
        
1513
        // Get original source EntityIDs
1514
        auto stimulus_time_frame = dm.getTime(TimeKey("stimulus_time"));
1✔
1515
        auto original_stimulus_intervals = stimulus_source->getIntervalsInRange(
1✔
1516
            TimeFrameIndex(0), TimeFrameIndex(20), stimulus_time_frame.get());
1✔
1517
        
1518
        std::cout << "Source data has " << original_stimulus_intervals.size() << " stimulus intervals" << std::endl;
1✔
1519
        
1520
        // Create row selector from behavior intervals
1521
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1522
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
1523
            TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
1524
        
1525
        std::vector<TimeFrameInterval> row_intervals;
1✔
1526
        for (const auto& interval : behavior_intervals) {
5✔
1527
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
1528
        }
1529
        
1530
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1531
        
1532
        // Create CountOverlaps computer to get all overlapping EntityIDs
1533
        auto count_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
1534
            stimulus_source, 
1535
            IntervalOverlapOperation::CountOverlaps, 
1✔
1536
            "StimulusIntervals"
1537
        );
1✔
1538
        
1539
        // Create table with the computer
1540
        TableViewBuilder builder(dme);
1✔
1541
        builder.setRowSelector(std::move(row_selector));
1✔
1542
        builder.addColumn<int64_t>("StimulusOverlaps", std::move(count_computer));
3✔
1543
        
1544
        TableView table = builder.build();
1✔
1545
        
1546
        // Get all EntityIDs from the column
1547
        ColumnEntityIds column_entity_ids = table.getColumnEntityIds("StimulusOverlaps");
3✔
1548
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(column_entity_ids));
1✔
1549
        
1550
        auto complex_entity_ids = std::get<std::vector<std::vector<EntityId>>>(column_entity_ids);
1✔
1551
        
1552
        // Collect all unique EntityIDs from the table
1553
        std::set<EntityId> table_entity_ids;
1✔
1554
        for (const auto& row_entity_ids : complex_entity_ids) {
5✔
1555
            for (const auto& entity_id : row_entity_ids) {
4✔
UNCOV
1556
                table_entity_ids.insert(entity_id);
×
1557
            }
1558
        }
1559
        
1560
        std::cout << "Table extracted " << table_entity_ids.size() << " unique EntityIDs" << std::endl;
1✔
1561
        
1562
        // Get original EntityIDs directly from the source data for verification
1563
        auto stimulus_data = dm.getData<DigitalIntervalSeries>("StimulusIntervals");
3✔
1564
        REQUIRE(stimulus_data != nullptr);
1✔
1565
        
1566
        auto source_stimulus_entity_ids = stimulus_data->getEntityIds();
1✔
1567
        std::cout << "Source stimulus data has " << source_stimulus_entity_ids.size() << " EntityIDs" << std::endl;
1✔
1568
        
1569
        // Debug: Print source EntityIDs
1570
        INFO("Source EntityIDs from StimulusIntervals:");
1✔
1571
        for (size_t i = 0; i < source_stimulus_entity_ids.size(); ++i) {
1✔
NEW
1572
            INFO("  Source EntityID[" << i << "] = " << source_stimulus_entity_ids[i]);
×
NEW
1573
        }
×
1574
        
1575
        // Debug: Print table EntityIDs
1576
        INFO("Table EntityIDs from IntervalOverlapComputer:");
1✔
1577
        for (const auto& entity_id : table_entity_ids) {
1✔
NEW
1578
            INFO("  Table EntityID = " << entity_id);
×
NEW
1579
        }
×
1580
        
1581
        // Verify that extracted EntityIDs are a subset of source EntityIDs
1582
        // (Not all source EntityIDs may appear in the table due to overlap filtering)
1583
        for (const auto& table_entity_id : table_entity_ids) {
1✔
NEW
1584
            bool found = std::find(source_stimulus_entity_ids.begin(), 
×
1585
                                 source_stimulus_entity_ids.end(), 
NEW
1586
                                 table_entity_id) != source_stimulus_entity_ids.end();
×
NEW
1587
            REQUIRE(found);
×
NEW
1588
            INFO("✓ Table EntityID " << table_entity_id << " found in source data");
×
NEW
1589
        }
×
1590
        
1591
        // Verify all EntityIDs are valid (non-zero)
1592
        for (const auto& entity_id : table_entity_ids) {
1✔
NEW
1593
            REQUIRE(entity_id != 0);
×
1594
        }
1595
        
1596
        // Verify cell-level EntityIDs match column-level EntityIDs
1597
        for (size_t row = 0; row < table.getRowCount(); ++row) {
5✔
1598
            auto cell_entity_ids = table.getCellEntityIds("StimulusOverlaps", row);
12✔
1599
            REQUIRE(cell_entity_ids == complex_entity_ids[row]);
4✔
1600
        }
4✔
1601
        
1602
        std::cout << "✓ EntityID round trip test passed" << std::endl;
1✔
1603
        std::cout << "  - All EntityIDs are valid and come from source data" << std::endl;
1✔
1604
        std::cout << "  - Cell-level extraction matches column-level extraction" << std::endl;
1✔
1605
        std::cout << "  - Extracted EntityIDs verified against original source data" << std::endl;
1✔
1606
    }
5✔
1607
}
4✔
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