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

paulmthompson / WhiskerToolbox / 17920603410

22 Sep 2025 03:39PM UTC coverage: 71.97% (-0.05%) from 72.02%
17920603410

push

github

paulmthompson
all tests pass

277 of 288 new or added lines in 8 files covered. (96.18%)

520 existing lines in 35 files now uncovered.

40275 of 55961 relevant lines covered (71.97%)

1225.8 hits per line

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

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

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

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

22
#include <algorithm>
23
#include <cstdint>
24
#include <memory>
25
#include <nlohmann/json.hpp>
26
#include <numeric>
27
#include <vector>
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
        behavior_intervals->setIdentityContext("BehaviorPeriods", m_data_manager->getEntityRegistry());
39✔
109
        behavior_intervals->rebuildAllEntityIds();
13✔
110

111
        m_data_manager->setData<DigitalIntervalSeries>("BehaviorPeriods", behavior_intervals, TimeKey("behavior_time"));
39✔
112
    }
26✔
113

114
    /**
115
     * @brief Create stimulus intervals (column intervals for testing)
116
     */
117
    void createStimulusIntervals() {
13✔
118
        // Create stimulus presentation periods
119
        auto stimulus_intervals = std::make_shared<DigitalIntervalSeries>();
13✔
120

121
        // Stimulus 1: time 5-15 (overlaps with exploration period 1)
122
        stimulus_intervals->addEvent(TimeFrameIndex(1), TimeFrameIndex(3));// Index 1-3 = time 5-15
13✔
123

124
        // Stimulus 2: time 20-30 (overlaps with end of exploration 1 and start of rest)
125
        stimulus_intervals->addEvent(TimeFrameIndex(4), TimeFrameIndex(6));// Index 4-6 = time 20-30
13✔
126

127
        // Stimulus 3: time 45-55 (overlaps with start of exploration period 2)
128
        stimulus_intervals->addEvent(TimeFrameIndex(9), TimeFrameIndex(11));// Index 9-11 = time 45-55
13✔
129

130
        // Stimulus 4: time 85-95 (overlaps with social interaction)
131
        stimulus_intervals->addEvent(TimeFrameIndex(17), TimeFrameIndex(19));// Index 17-19 = time 85-95
13✔
132

133
        stimulus_intervals->setIdentityContext("StimulusIntervals", m_data_manager->getEntityRegistry());
39✔
134
        stimulus_intervals->rebuildAllEntityIds();
13✔
135

136
        m_data_manager->setData<DigitalIntervalSeries>("StimulusIntervals", stimulus_intervals, TimeKey("stimulus_time"));
39✔
137
    }
26✔
138
};
139

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

155
        // Initialize TablePipeline with the existing TableRegistry
156
        m_table_pipeline = std::make_unique<TablePipeline>(m_table_registry_ptr, &getDataManager());
11✔
157
    }
11✔
158

159
    ~IntervalTableRegistryTestFixture() = default;
11✔
160

161
    /**
162
     * @brief Get the TableRegistry instance
163
     * @return Reference to the TableRegistry
164
     */
165
    TableRegistry & getTableRegistry() { return *m_table_registry_ptr; }
6✔
166

167
    /**
168
     * @brief Get the TableRegistry instance (const version)
169
     * @return Const reference to the TableRegistry
170
     */
171
    TableRegistry const & getTableRegistry() const { return *m_table_registry_ptr; }
172

173
    /**
174
     * @brief Get a pointer to the TableRegistry
175
     * @return Raw pointer to the TableRegistry
176
     */
177
    TableRegistry * getTableRegistryPtr() { return m_table_registry_ptr; }
178

179
    /**
180
     * @brief Get the TablePipeline instance
181
     * @return Reference to the TablePipeline
182
     */
183
    TablePipeline & getTablePipeline() { return *m_table_pipeline; }
4✔
184

185
    /**
186
     * @brief Get the TablePipeline instance (const version)
187
     * @return Const reference to the TablePipeline
188
     */
189
    TablePipeline const & getTablePipeline() const { return *m_table_pipeline; }
190

191
    /**
192
     * @brief Get a pointer to the TablePipeline
193
     * @return Raw pointer to the TablePipeline
194
     */
195
    TablePipeline * getTablePipelinePtr() { return m_table_pipeline.get(); }
196

197
    /**
198
     * @brief Get the DataManagerExtension instance
199
     */
200
    std::shared_ptr<DataManagerExtension> getDataManagerExtension() {
201
        if (!m_data_manager_extension) {
202
            m_data_manager_extension = std::make_shared<DataManagerExtension>(getDataManager());
203
        }
204
        return m_data_manager_extension;
205
    }
206

207
private:
208
    TableRegistry * m_table_registry_ptr;// Points to DataManager's TableRegistry
209
    std::unique_ptr<TablePipeline> m_table_pipeline;
210
    std::shared_ptr<DataManagerExtension> m_data_manager_extension;// Lazy-initialized
211
};
212

213
// Mock implementation of IIntervalSource for testing
214
class MockIntervalSource : public IIntervalSource {
215
public:
216
    MockIntervalSource(std::string name,
19✔
217
                       std::shared_ptr<TimeFrame> timeFrame,
218
                       std::vector<Interval> intervals)
219
        : m_name(std::move(name)),
19✔
220
          m_timeFrame(std::move(timeFrame)),
19✔
221
          m_intervals(std::move(intervals)) {}
38✔
222

223
    [[nodiscard]] auto getName() const -> std::string const & override {
×
224
        return m_name;
×
225
    }
226

227
    [[nodiscard]] auto getTimeFrame() const -> std::shared_ptr<TimeFrame> override {
23✔
228
        return m_timeFrame;
23✔
229
    }
230

231
    [[nodiscard]] auto size() const -> size_t override {
×
232
        return m_intervals.size();
×
233
    }
234

235
    auto getIntervals() -> std::vector<Interval> override {
12✔
236
        return m_intervals;
12✔
237
    }
238

NEW
239
    auto getIntervalsInRange(TimeFrameIndex start, TimeFrameIndex end,
×
240
                             TimeFrame const * target_timeFrame) -> std::vector<Interval> override {
UNCOV
241
        std::vector<Interval> result;
×
242

243
        // Convert TimeFrameIndex to time values for comparison
UNCOV
244
        auto startTime = target_timeFrame->getTimeAtIndex(start);
×
UNCOV
245
        auto endTime = target_timeFrame->getTimeAtIndex(end);
×
246

NEW
247
        for (auto const & interval: m_intervals) {
×
248
            // Convert interval indices to time values using our timeframe
UNCOV
249
            auto intervalStartTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.start));
×
UNCOV
250
            auto intervalEndTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.end));
×
251

252
            // Check if intervals overlap in time
UNCOV
253
            if (intervalStartTime <= endTime && startTime <= intervalEndTime) {
×
UNCOV
254
                result.push_back(interval);
×
255
            }
256
        }
257

UNCOV
258
        return result;
×
259
    }
×
260

261
    auto getIntervalsWithIdsInRange(TimeFrameIndex start, TimeFrameIndex end,
50✔
262
                                    TimeFrame const * target_timeFrame) -> std::vector<IntervalWithId> override {
263
        std::vector<IntervalWithId> result;
50✔
264

265
        // Convert TimeFrameIndex to time values for comparison
266
        auto startTime = target_timeFrame->getTimeAtIndex(start);
50✔
267
        auto endTime = target_timeFrame->getTimeAtIndex(end);
50✔
268

269
        for (auto const & interval: m_intervals) {
190✔
270
            // Convert interval indices to time values using our timeframe
271
            auto intervalStartTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.start));
140✔
272
            auto intervalEndTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.end));
140✔
273

274
            // Check if intervals overlap in time
275
            if (intervalStartTime <= endTime && startTime <= intervalEndTime) {
140✔
276
                result.push_back(IntervalWithId(interval, 0));
62✔
277
            }
278
        }
279

280
        return result;
50✔
UNCOV
281
    }
×
282

283
private:
284
    std::string m_name;
285
    std::shared_ptr<TimeFrame> m_timeFrame;
286
    std::vector<Interval> m_intervals;
287
};
288

289
TEST_CASE("DM - TV - IntervalOverlapComputer Basic Functionality", "[IntervalOverlapComputer]") {
5✔
290

291
    SECTION("AssignID operation - basic overlap detection") {
5✔
292
        // Create time frames
293
        std::vector<int> rowTimeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
294
        auto rowTimeFrame = std::make_shared<TimeFrame>(rowTimeValues);
1✔
295

296
        std::vector<int> colTimeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
297
        auto colTimeFrame = std::make_shared<TimeFrame>(colTimeValues);
1✔
298

299
        // Create column intervals (source intervals)
300
        std::vector<Interval> columnIntervals = {
1✔
301
                {0, 1},// Interval 0: time 0-1
302
                {3, 5},// Interval 1: time 3-5
303
                {7, 9} // Interval 2: time 7-9
304
        };
3✔
305

306
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
307
                "TestIntervals", colTimeFrame, columnIntervals);
1✔
308

309
        // Create row intervals (from execution plan)
310
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
311
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(1)),// Row 0: time 0-1 (should overlap with interval 0)
312
                TimeFrameInterval(TimeFrameIndex(3), TimeFrameIndex(4)),// Row 1: time 3-4 (should overlap with interval 1)
313
                TimeFrameInterval(TimeFrameIndex(8), TimeFrameIndex(8)),// Row 2: time 8-8 (should overlap with interval 2)
314
                TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(6)) // Row 3: time 6-6 (should not overlap with any)
315
        };
3✔
316

317
        ExecutionPlan plan(rowIntervals, rowTimeFrame);
1✔
318

319
        // Create the computer
320
        IntervalOverlapComputer<int64_t> computer(intervalSource,
1✔
321
                                                  IntervalOverlapOperation::AssignID,
322
                                                  "TestIntervals");
3✔
323

324
        // Compute the results
325
        auto [results, entity_ids] = computer.compute(plan);
1✔
326

327
        // Verify results
328
        REQUIRE(results.size() == 4);
1✔
329
        REQUIRE(results[0] == 0); // Row 0 overlaps with interval 0
1✔
330
        REQUIRE(results[1] == 1); // Row 1 overlaps with interval 1 (returns index of last matching interval)
1✔
331
        REQUIRE(results[2] == 2); // Row 2 overlaps with interval 2 (returns index of last matching interval)
1✔
332
        REQUIRE(results[3] == -1);// Row 3 doesn't overlap with any interval
1✔
333
    }
6✔
334

335
    SECTION("CountOverlaps operation - basic overlap counting") {
5✔
336
        // Create time frames
337
        std::vector<int> rowTimeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
338
        auto rowTimeFrame = std::make_shared<TimeFrame>(rowTimeValues);
1✔
339

340
        std::vector<int> colTimeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
341
        auto colTimeFrame = std::make_shared<TimeFrame>(colTimeValues);
1✔
342

343
        // Create column intervals with some overlaps
344
        std::vector<Interval> columnIntervals = {
1✔
345
                {0, 2},// Interval 0: time 0-2
346
                {1, 3},// Interval 1: time 1-3 (overlaps with interval 0)
347
                {5, 7},// Interval 2: time 5-7
348
                {6, 8} // Interval 3: time 6-8 (overlaps with interval 2)
349
        };
3✔
350

351
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
352
                "TestIntervals", colTimeFrame, columnIntervals);
1✔
353

354
        // Create row intervals
355
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
356
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2)),// Row 0: time 0-2 (overlaps with intervals 0,1)
357
                TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(3)),// Row 1: time 1-3 (overlaps with intervals 0,1)
358
                TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(7)),// Row 2: time 6-7 (overlaps with intervals 2,3)
359
                TimeFrameInterval(TimeFrameIndex(9), TimeFrameIndex(9)) // Row 3: time 9-9 (no overlaps)
360
        };
3✔
361

362
        ExecutionPlan plan(rowIntervals, rowTimeFrame);
1✔
363

364
        // Create the computer
365
        IntervalOverlapComputer<int64_t> computer(intervalSource,
1✔
366
                                                  IntervalOverlapOperation::CountOverlaps,
367
                                                  "TestIntervals");
3✔
368

369
        // Compute the results
370
        auto [results, entity_ids] = computer.compute(plan);
1✔
371

372
        // Verify results
373
        REQUIRE(results.size() == 4);
1✔
374
        // Note: The actual counting logic depends on the countOverlappingIntervals implementation
375
        // These tests verify the computer calls the counting function correctly
376
        REQUIRE(results[0] >= 0);// Row 0 should have some overlaps
1✔
377
        REQUIRE(results[1] >= 0);// Row 1 should have some overlaps
1✔
378
        REQUIRE(results[2] >= 0);// Row 2 should have some overlaps
1✔
379
        REQUIRE(results[3] >= 0);// Row 3 might have no overlaps
1✔
380
    }
6✔
381

382
    SECTION("Empty intervals handling") {
5✔
383
        // Create time frames
384
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
385
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
386

387
        // Create empty column intervals
388
        std::vector<Interval> columnIntervals;
1✔
389
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
390
                "EmptyIntervals", timeFrame, columnIntervals);
1✔
391

392
        // Create row intervals
393
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
394
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(1)),
395
                TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(3))};
3✔
396

397
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
398

399
        // Test AssignID operation
400
        IntervalOverlapComputer<int64_t> assignComputer(intervalSource,
1✔
401
                                                        IntervalOverlapOperation::AssignID,
402
                                                        "EmptyIntervals");
3✔
403

404
        auto [assignResults, assignEntity_ids] = assignComputer.compute(plan);
1✔
405

406
        REQUIRE(assignResults.size() == 2);
1✔
407
        REQUIRE(assignResults[0] == -1);// No intervals to assign
1✔
408
        REQUIRE(assignResults[1] == -1);// No intervals to assign
1✔
409

410
        // Test CountOverlaps operation
411
        IntervalOverlapComputer<int64_t> countComputer(intervalSource,
1✔
412
                                                       IntervalOverlapOperation::CountOverlaps,
413
                                                       "EmptyIntervals");
3✔
414

415
        auto [countResults, countEntity_ids] = countComputer.compute(plan);
1✔
416

417
        REQUIRE(countResults.size() == 2);
1✔
418
        REQUIRE(countResults[0] == 0);// No overlaps
1✔
419
        REQUIRE(countResults[1] == 0);// No overlaps
1✔
420
    }
6✔
421

422
    SECTION("Single interval scenarios") {
5✔
423
        // Create time frames
424
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
425
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
426

427
        // Create single column interval
428
        std::vector<Interval> columnIntervals = {
1✔
429
                {1, 3}// Single interval: time 1-3
430
        };
3✔
431

432
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
433
                "SingleInterval", timeFrame, columnIntervals);
1✔
434

435
        // Create various row intervals
436
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
437
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(0)),// Before interval
438
                TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(2)),// Overlaps with interval
439
                TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(3)),// Overlaps with interval
440
                TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(5)) // After interval
441
        };
3✔
442

443
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
444

445
        // Test AssignID operation
446
        IntervalOverlapComputer<int64_t> assignComputer(intervalSource,
1✔
447
                                                        IntervalOverlapOperation::AssignID,
448
                                                        "SingleInterval");
3✔
449

450
        auto [assignResults, assignEntity_ids] = assignComputer.compute(plan);
1✔
451

452
        REQUIRE(assignResults.size() == 4);
1✔
453
        REQUIRE(assignResults[0] == -1);// No overlap
1✔
454
        REQUIRE(assignResults[1] == 0); // Overlaps with interval 0
1✔
455
        REQUIRE(assignResults[2] == 0); // Overlaps with interval 0
1✔
456
        REQUIRE(assignResults[3] == -1);// No overlap
1✔
457
    }
6✔
458

459
    SECTION("Edge case: identical intervals") {
5✔
460
        // Create time frames
461
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
462
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
463

464
        // Create column intervals
465
        std::vector<Interval> columnIntervals = {
1✔
466
                {1, 3},// Interval 0
467
                {1, 3} // Interval 1 (identical to interval 0)
468
        };
3✔
469

470
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
471
                "IdenticalIntervals", timeFrame, columnIntervals);
1✔
472

473
        // Create row interval that matches exactly
474
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
475
                TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(3))};
3✔
476

477
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
478

479
        // Test AssignID operation (should return the last matching interval)
480
        IntervalOverlapComputer<int64_t> assignComputer(intervalSource,
1✔
481
                                                        IntervalOverlapOperation::AssignID,
482
                                                        "IdenticalIntervals");
3✔
483

484
        auto [assignResults, assignEntity_ids] = assignComputer.compute(plan);
1✔
485

486
        REQUIRE(assignResults.size() == 1);
1✔
487
        REQUIRE(assignResults[0] == 1);// Should return the last matching interval (index 1)
1✔
488
    }
6✔
489
}
5✔
490

491
TEST_CASE("DM - TV - IntervalOverlapComputer Error Handling", "[IntervalOverlapComputer][Error]") {
1✔
492

493
    SECTION("ExecutionPlan without intervals throws exception") {
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 execution plan with indices instead of intervals
504
        std::vector<TimeFrameIndex> indices = {TimeFrameIndex(0), TimeFrameIndex(1)};
3✔
505
        ExecutionPlan plan(indices, timeFrame);
1✔
506

507
        // Create the computer
508
        IntervalOverlapComputer<int64_t> computer(intervalSource,
1✔
509
                                                  IntervalOverlapOperation::AssignID,
510
                                                  "TestIntervals");
3✔
511

512
        // Should throw an exception
513
        REQUIRE_THROWS_AS(computer.compute(plan), std::runtime_error);
2✔
514
    }
2✔
515
}
1✔
516

517
TEST_CASE("DM - TV - IntervalOverlapComputer Template Types", "[IntervalOverlapComputer][Templates]") {
1✔
518

519
    SECTION("Test with different numeric types") {
1✔
520
        // Create time frames
521
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
522
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
523

524
        // Create column intervals
525
        std::vector<Interval> columnIntervals = {{1, 3}};
3✔
526
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
527
                "TestIntervals", timeFrame, columnIntervals);
1✔
528

529
        // Create row intervals
530
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
531
                TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(2))};
3✔
532

533
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
534

535
        // Test with int64_t
536
        IntervalOverlapComputer<int64_t> intComputer(intervalSource,
1✔
537
                                                     IntervalOverlapOperation::AssignID,
538
                                                     "TestIntervals");
3✔
539

540
        auto [intResults, intEntity_ids] = intComputer.compute(plan);
1✔
541
        REQUIRE(intResults.size() == 1);
1✔
542
        REQUIRE(intResults[0] == 0);
1✔
543

544
        // Test with size_t for CountOverlaps
545
        IntervalOverlapComputer<size_t> sizeComputer(intervalSource,
1✔
546
                                                     IntervalOverlapOperation::CountOverlaps,
547
                                                     "TestIntervals");
3✔
548

549
        auto [sizeResults, sizeEntity_ids] = sizeComputer.compute(plan);
1✔
550
        REQUIRE(sizeResults.size() == 1);
1✔
551
        REQUIRE(sizeResults[0] >= 0);
1✔
552
    }
2✔
553
}
1✔
554

555
TEST_CASE("DM - TV - IntervalOverlapComputer Dependency Tracking", "[IntervalOverlapComputer][Dependencies]") {
1✔
556

557
    SECTION("getSourceDependency returns correct source name") {
1✔
558
        // Create minimal setup
559
        std::vector<int> timeValues = {0, 1, 2};
3✔
560
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
561

562
        std::vector<Interval> columnIntervals = {{0, 1}};
3✔
563
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
564
                "TestSource", timeFrame, columnIntervals);
1✔
565

566
        // Create computer
567
        IntervalOverlapComputer<int64_t> computer(intervalSource,
1✔
568
                                                  IntervalOverlapOperation::AssignID,
569
                                                  "TestSourceName");
3✔
570

571
        // Test source dependency
572
        REQUIRE(computer.getSourceDependency() == "TestSourceName");
1✔
573
    }
2✔
574
}
1✔
575

576
TEST_CASE("DM - TV - IntervalOverlapComputer Complex Scenarios", "[IntervalOverlapComputer][Complex]") {
1✔
577

578
    SECTION("Multiple overlapping intervals with different time scales") {
1✔
579
        // Create time frames with different scales
580
        std::vector<int> rowTimeValues = {0, 10, 20, 30, 40, 50};
3✔
581
        auto rowTimeFrame = std::make_shared<TimeFrame>(rowTimeValues);
1✔
582

583
        std::vector<int> colTimeValues = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50};
3✔
584
        auto colTimeFrame = std::make_shared<TimeFrame>(colTimeValues);
1✔
585

586
        // Create column intervals
587
        std::vector<Interval> columnIntervals = {
1✔
588
                {0, 2},// Interval 0: time 0-10
589
                {1, 4},// Interval 1: time 5-20 (overlaps with interval 0)
590
                {3, 6},// Interval 2: time 15-30 (overlaps with interval 1)
591
                {8, 10}// Interval 3: time 40-50
592
        };
3✔
593

594
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
595
                "ComplexIntervals", colTimeFrame, columnIntervals);
1✔
596

597
        // Create row intervals
598
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
599
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(1)),// Row 0: time 0-10
600
                TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(3)),// Row 1: time 20-30
601
                TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(5)) // Row 2: time 40-50
602
        };
3✔
603

604
        ExecutionPlan plan(rowIntervals, rowTimeFrame);
1✔
605

606
        // Test AssignID operation
607
        IntervalOverlapComputer<int64_t> assignComputer(intervalSource,
1✔
608
                                                        IntervalOverlapOperation::AssignID,
609
                                                        "ComplexIntervals");
3✔
610

611
        auto [assignResults, assignEntity_ids] = assignComputer.compute(plan);
1✔
612

613
        REQUIRE(assignResults.size() == 3);
1✔
614
        // Results depend on the specific overlap logic implementation
615
        // The test verifies the computer executes without errors
616

617
        // Test CountOverlaps operation
618
        IntervalOverlapComputer<int64_t> countComputer(intervalSource,
1✔
619
                                                       IntervalOverlapOperation::CountOverlaps,
620
                                                       "ComplexIntervals");
3✔
621

622
        auto [countResults, countEntity_ids] = countComputer.compute(plan);
1✔
623

624
        REQUIRE(countResults.size() == 3);
1✔
625
        // All results should be non-negative
626
        for (auto result: countResults) {
4✔
627
            REQUIRE(result >= 0);
3✔
628
        }
629

630
        IntervalOverlapComputer<size_t> countComputer_size_t(intervalSource,
1✔
631
                                                             IntervalOverlapOperation::CountOverlaps,
632
                                                             "ComplexIntervals");
3✔
633

634
        auto [countResults_size_t, countEntity_ids_size_t] = countComputer_size_t.compute(plan);
1✔
635

636
        REQUIRE(countResults_size_t.size() == 3);
1✔
637
        // All results should be non-negative
638
        for (auto result: countResults_size_t) {
4✔
639
            REQUIRE(result >= 0);
3✔
640
        }
641
    }
2✔
642
}
1✔
643

644
// Test the standalone utility functions
645
TEST_CASE("DM - TV - IntervalOverlapComputer Utility Functions", "[IntervalOverlapComputer][Utilities]") {
2✔
646

647
    SECTION("intervalsOverlap function") {
2✔
648
        TimeFrameInterval a(TimeFrameIndex(0), TimeFrameIndex(5));
1✔
649
        TimeFrameInterval b(TimeFrameIndex(3), TimeFrameIndex(8));
1✔
650
        TimeFrameInterval c(TimeFrameIndex(6), TimeFrameIndex(10));
1✔
651

652
        // Test overlapping intervals
653
        REQUIRE(intervalsOverlap(a, b) == true);
1✔
654

655
        // Test non-overlapping intervals
656
        REQUIRE(intervalsOverlap(a, c) == false);
1✔
657

658
        // Test identical intervals
659
        REQUIRE(intervalsOverlap(a, a) == true);
1✔
660
    }
2✔
661

662
    SECTION("findContainingInterval function") {
2✔
663
        TimeFrameInterval rowInterval(TimeFrameIndex(2), TimeFrameIndex(4));
1✔
664

665
        std::vector<Interval> columnIntervals = {
1✔
666
                {0, 1},// Interval 0: doesn't contain row interval
667
                {1, 5},// Interval 1: contains row interval
668
                {3, 7},// Interval 2: overlaps but doesn't fully contain
669
                {6, 8} // Interval 3: doesn't contain row interval
670
        };
3✔
671

672
        auto result = findContainingInterval(rowInterval, columnIntervals);
1✔
673

674
        // Should return the index of the interval that contains the row interval
675
        // The exact behavior depends on the implementation
676
        REQUIRE(result >= -1);// -1 means no containing interval found
1✔
677
    }
3✔
678

679
    /*
680
    SECTION("countOverlappingIntervals function") {
681
        TimeFrameInterval rowInterval(TimeFrameIndex(2), TimeFrameIndex(4));
682
        
683
        std::vector<Interval> columnIntervals = {
684
            {0, 1},   // Interval 0: doesn't overlap
685
            {1, 3},   // Interval 1: overlaps
686
            {3, 5},   // Interval 2: overlaps
687
            {6, 8}    // Interval 3: doesn't overlap
688
        };
689
        
690
        auto result = countOverlappingIntervals(rowInterval, columnIntervals);
691
        
692
        // Should count overlapping intervals
693
        REQUIRE(result >= 0);
694
        REQUIRE(result <= static_cast<int64_t>(columnIntervals.size()));
695
    }
696
        */
697
}
2✔
698

699
TEST_CASE_METHOD(IntervalOverlapTestFixture, "DM - TV - IntervalOverlapComputer with DataManager fixture", "[IntervalOverlapComputer][DataManager][Fixture]") {
2✔
700

701
    SECTION("Test with behavior and stimulus intervals from fixture") {
2✔
702
        auto & dm = getDataManager();
1✔
703
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
704

705
        // Get the interval sources from the DataManager
706
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
707
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
708

709
        REQUIRE(behavior_source != nullptr);
1✔
710
        REQUIRE(stimulus_source != nullptr);
1✔
711

712
        // Create row selector from behavior intervals
713
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
714
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
715
                TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
716

717
        REQUIRE(behavior_intervals.size() == 4);// 4 behavior periods
1✔
718

719
        // Convert to TimeFrameIntervals for row selector
720
        std::vector<TimeFrameInterval> row_intervals;
1✔
721
        for (auto const & interval: behavior_intervals) {
5✔
722
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
723
        }
724

725
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
726

727
        // Create TableView builder
728
        TableViewBuilder builder(dme);
1✔
729
        builder.setRowSelector(std::move(row_selector));
1✔
730

731
        // Test AssignID operation
732
        builder.addColumn<int64_t>("Stimulus_ID",
5✔
733
                                   std::make_unique<IntervalOverlapComputer<int64_t>>(stimulus_source,
3✔
734
                                                                                      IntervalOverlapOperation::AssignID, "StimulusIntervals"));
2✔
735

736
        // Test CountOverlaps operation
737
        builder.addColumn<int64_t>("Stimulus_Count",
5✔
738
                                   std::make_unique<IntervalOverlapComputer<int64_t>>(stimulus_source,
3✔
739
                                                                                      IntervalOverlapOperation::CountOverlaps, "StimulusIntervals"));
2✔
740

741
        // Build the table
742
        TableView table = builder.build();
1✔
743

744
        // Verify table structure
745
        REQUIRE(table.getRowCount() == 4);
1✔
746
        REQUIRE(table.getColumnCount() == 2);
1✔
747
        REQUIRE(table.hasColumn("Stimulus_ID"));
3✔
748
        REQUIRE(table.hasColumn("Stimulus_Count"));
3✔
749

750
        // Get the column data
751
        auto stimulus_ids = table.getColumnValues<int64_t>("Stimulus_ID");
3✔
752
        auto stimulus_counts = table.getColumnValues<int64_t>("Stimulus_Count");
3✔
753

754
        REQUIRE(stimulus_ids.size() == 4);
1✔
755
        REQUIRE(stimulus_counts.size() == 4);
1✔
756

757
        // Verify the overlap results
758
        // Expected overlaps based on our test data:
759
        // Behavior 1 (10-25): overlaps with Stimulus 1 (5-15) and Stimulus 2 (20-30)
760
        // Behavior 2 (30-40): overlaps with Stimulus 2 (20-30)
761
        // Behavior 3 (50-70): overlaps with Stimulus 3 (45-55)
762
        // Behavior 4 (80-95): overlaps with Stimulus 4 (85-95)
763

764
        // Note: Actual results depend on the cross-timeframe conversion implementation
765
        // These tests verify the computer executes without errors and produces reasonable results
766
        for (size_t i = 0; i < 4; ++i) {
5✔
767
            REQUIRE(stimulus_ids[i] >= -1);  // -1 means no overlap, >= 0 means valid stimulus ID
4✔
768
            REQUIRE(stimulus_counts[i] >= 0);// Should be non-negative count
4✔
769
            REQUIRE(stimulus_counts[i] <= 4);// Cannot exceed total number of stimuli
4✔
770
        }
771
    }
3✔
772

773
    SECTION("Test cross-timeframe overlap detection") {
2✔
774
        auto & dm = getDataManager();
1✔
775
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
776

777
        // Get sources from different timeframes
778
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");  // behavior_time frame
3✔
779
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");// stimulus_time frame
3✔
780

781
        REQUIRE(behavior_source != nullptr);
1✔
782
        REQUIRE(stimulus_source != nullptr);
1✔
783

784
        // Verify they have different timeframes
785
        auto behavior_tf = behavior_source->getTimeFrame();
1✔
786
        auto stimulus_tf = stimulus_source->getTimeFrame();
1✔
787
        REQUIRE(behavior_tf != stimulus_tf);
1✔
788
        REQUIRE(behavior_tf->getTotalFrameCount() == 101);// behavior_time: 0-100
1✔
789
        REQUIRE(stimulus_tf->getTotalFrameCount() == 21); // stimulus_time: 0,5,10,...,100
1✔
790

791
        // Create a simple test with one behavior interval
792
        std::vector<TimeFrameInterval> test_intervals = {
1✔
793
                TimeFrameInterval(TimeFrameIndex(10), TimeFrameIndex(25))// Behavior period 1
794
        };
3✔
795

796
        auto row_selector = std::make_unique<IntervalSelector>(test_intervals, behavior_tf);
1✔
797

798
        TableViewBuilder builder(dme);
1✔
799
        builder.setRowSelector(std::move(row_selector));
1✔
800

801
        // Add overlap analysis columns
802
        builder.addColumn<int64_t>("Stimulus_ID",
5✔
803
                                   std::make_unique<IntervalOverlapComputer<int64_t>>(stimulus_source,
3✔
804
                                                                                      IntervalOverlapOperation::AssignID, "StimulusIntervals"));
2✔
805

806
        builder.addColumn<int64_t>("Stimulus_Count",
5✔
807
                                   std::make_unique<IntervalOverlapComputer<int64_t>>(stimulus_source,
3✔
808
                                                                                      IntervalOverlapOperation::CountOverlaps, "StimulusIntervals"));
2✔
809

810
        // Build and verify the table
811
        TableView table = builder.build();
1✔
812

813
        REQUIRE(table.getRowCount() == 1);
1✔
814
        REQUIRE(table.getColumnCount() == 2);
1✔
815

816
        auto stimulus_ids = table.getColumnValues<int64_t>("Stimulus_ID");
3✔
817
        auto stimulus_counts = table.getColumnValues<int64_t>("Stimulus_Count");
3✔
818

819
        REQUIRE(stimulus_ids.size() == 1);
1✔
820
        REQUIRE(stimulus_counts.size() == 1);
1✔
821

822
        // Verify results are reasonable (exact values depend on timeframe conversion)
823
        REQUIRE(stimulus_ids[0] >= -1);
1✔
824
        REQUIRE(stimulus_counts[0] >= 0);
1✔
825
        REQUIRE(stimulus_counts[0] <= 4);
1✔
826

827
        std::cout << "Cross-timeframe test - Stimulus ID: " << stimulus_ids[0]
1✔
828
                  << ", Count: " << stimulus_counts[0] << std::endl;
1✔
829
    }
3✔
830
}
2✔
831

832
TEST_CASE_METHOD(IntervalTableRegistryTestFixture, "DM - TV - IntervalOverlapComputer via ComputerRegistry", "[IntervalOverlapComputer][Registry]") {
3✔
833

834
    SECTION("Verify IntervalOverlapComputer is registered in ComputerRegistry") {
3✔
835
        auto & registry = getTableRegistry().getComputerRegistry();
1✔
836

837
        // Check that all interval overlap computers are registered
838
        auto assign_id_info = registry.findComputerInfo("Interval Overlap Assign ID");
3✔
839
        auto count_info = registry.findComputerInfo("Interval Overlap Count");
3✔
840
        auto assign_start_info = registry.findComputerInfo("Interval Overlap Assign Start");
3✔
841
        auto assign_end_info = registry.findComputerInfo("Interval Overlap Assign End");
3✔
842

843
        REQUIRE(assign_id_info != nullptr);
1✔
844
        REQUIRE(count_info != nullptr);
1✔
845
        REQUIRE(assign_start_info != nullptr);
1✔
846
        REQUIRE(assign_end_info != nullptr);
1✔
847

848
        // Verify computer info details
849
        REQUIRE(assign_id_info->name == "Interval Overlap Assign ID");
1✔
850
        REQUIRE(assign_id_info->outputType == typeid(int64_t));
1✔
851
        REQUIRE(assign_id_info->outputTypeName == "int64_t");
1✔
852
        REQUIRE(assign_id_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
853
        REQUIRE(assign_id_info->requiredSourceType == typeid(std::shared_ptr<IIntervalSource>));
1✔
854

855
        REQUIRE(count_info->name == "Interval Overlap Count");
1✔
856
        REQUIRE(count_info->outputType == typeid(int64_t));
1✔
857
        REQUIRE(count_info->outputTypeName == "int64_t");
1✔
858
        REQUIRE(count_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
859
        REQUIRE(count_info->requiredSourceType == typeid(std::shared_ptr<IIntervalSource>));
1✔
860
    }
3✔
861

862
    SECTION("Create IntervalOverlapComputer via ComputerRegistry") {
3✔
863
        auto & dm = getDataManager();
1✔
864
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
865
        auto & registry = getTableRegistry().getComputerRegistry();
1✔
866

867
        // Get stimulus source for testing
868
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
869
        REQUIRE(stimulus_source != nullptr);
1✔
870

871
        // Create computers via registry
872
        std::map<std::string, std::string> empty_params;
1✔
873

874
        auto assign_id_computer = registry.createTypedComputer<int64_t>(
1✔
875
                "Interval Overlap Assign ID", stimulus_source, empty_params);
3✔
876
        auto count_computer = registry.createTypedComputer<int64_t>(
1✔
877
                "Interval Overlap Count", stimulus_source, empty_params);
3✔
878

879
        REQUIRE(assign_id_computer != nullptr);
1✔
880
        REQUIRE(count_computer != nullptr);
1✔
881

882
        // Test that the created computers work correctly
883
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
884

885
        // Create a simple test interval
886
        std::vector<TimeFrameInterval> test_intervals = {
1✔
887
                TimeFrameInterval(TimeFrameIndex(50), TimeFrameIndex(70))// Behavior period 3
888
        };
3✔
889

890
        auto row_selector = std::make_unique<IntervalSelector>(test_intervals, behavior_time_frame);
1✔
891

892
        TableViewBuilder builder(dme);
1✔
893
        builder.setRowSelector(std::move(row_selector));
1✔
894

895
        // Use the registry-created computers
896
        builder.addColumn("RegistryAssignID", std::move(assign_id_computer));
3✔
897
        builder.addColumn("RegistryCount", std::move(count_computer));
3✔
898

899
        // Build and verify the table
900
        TableView table = builder.build();
1✔
901

902
        REQUIRE(table.getRowCount() == 1);
1✔
903
        REQUIRE(table.getColumnCount() == 2);
1✔
904
        REQUIRE(table.hasColumn("RegistryAssignID"));
3✔
905
        REQUIRE(table.hasColumn("RegistryCount"));
3✔
906

907
        auto assign_ids = table.getColumnValues<int64_t>("RegistryAssignID");
3✔
908
        auto counts = table.getColumnValues<int64_t>("RegistryCount");
3✔
909

910
        REQUIRE(assign_ids.size() == 1);
1✔
911
        REQUIRE(counts.size() == 1);
1✔
912

913
        // Verify reasonable results
914
        REQUIRE(assign_ids[0] >= -1);
1✔
915
        REQUIRE(counts[0] >= 0);
1✔
916
        REQUIRE(counts[0] <= 4);
1✔
917

918
        std::cout << "Registry test - Assign ID: " << assign_ids[0]
1✔
919
                  << ", Count: " << counts[0] << std::endl;
1✔
920
    }
4✔
921

922
    SECTION("Compare registry-created vs direct-created computers") {
3✔
923
        auto & dm = getDataManager();
1✔
924
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
925
        auto & registry = getTableRegistry().getComputerRegistry();
1✔
926

927
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
928
        REQUIRE(stimulus_source != nullptr);
1✔
929

930
        // Create computer via registry
931
        std::map<std::string, std::string> empty_params;
1✔
932
        auto registry_computer = registry.createTypedComputer<int64_t>(
1✔
933
                "Interval Overlap Count", stimulus_source, empty_params);
3✔
934

935
        // Create computer directly
936
        auto direct_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
937
                stimulus_source, IntervalOverlapOperation::CountOverlaps, "StimulusIntervals");
1✔
938

939
        REQUIRE(registry_computer != nullptr);
1✔
940
        REQUIRE(direct_computer != nullptr);
1✔
941

942
        // Test both computers with the same data
943
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
944
        std::vector<TimeFrameInterval> test_intervals = {
1✔
945
                TimeFrameInterval(TimeFrameIndex(50), TimeFrameIndex(70))};
3✔
946

947
        ExecutionPlan plan(test_intervals, behavior_time_frame);
1✔
948

949
        auto [registry_result, registryEntity_ids] = registry_computer->compute(plan);
1✔
950
        auto [direct_result, directEntity_ids] = direct_computer->compute(plan);
1✔
951

952
        REQUIRE(registry_result.size() == 1);
1✔
953
        REQUIRE(direct_result.size() == 1);
1✔
954

955
        // Results should be identical
956
        REQUIRE(registry_result[0] == direct_result[0]);
1✔
957

958
        std::cout << "Comparison test - Registry result: " << registry_result[0]
1✔
959
                  << ", Direct result: " << direct_result[0] << std::endl;
1✔
960
    }
4✔
961
}
3✔
962

963
TEST_CASE_METHOD(IntervalTableRegistryTestFixture, "DM - TV - IntervalOverlapComputer via JSON TablePipeline", "[IntervalOverlapComputer][JSON][Pipeline]") {
2✔
964

965
    SECTION("Test CountOverlaps operation via JSON pipeline") {
2✔
966
        // JSON configuration for testing IntervalOverlapComputer through TablePipeline
967
        char const * json_config = R"({
1✔
968
            "metadata": {
969
                "name": "Interval Overlap Test",
970
                "description": "Test JSON execution of IntervalOverlapComputer",
971
                "version": "1.0"
972
            },
973
            "tables": [
974
                {
975
                    "table_id": "interval_overlap_test",
976
                    "name": "Interval Overlap Test Table",
977
                    "description": "Test table using IntervalOverlapComputer",
978
                    "row_selector": {
979
                        "type": "interval",
980
                        "source": "BehaviorPeriods"
981
                    },
982
                    "columns": [
983
                        {
984
                            "name": "StimulusOverlapCount",
985
                            "description": "Count of stimulus events overlapping with each behavior period",
986
                            "data_source": "StimulusIntervals",
987
                            "computer": "Interval Overlap Count"
988
                        },
989
                        {
990
                            "name": "StimulusOverlapID",
991
                            "description": "ID of stimulus event overlapping with each behavior period",
992
                            "data_source": "StimulusIntervals",
993
                            "computer": "Interval Overlap Assign ID"
994
                        }
995
                    ]
996
                }
997
            ]
998
        })";
999

1000
        auto & pipeline = getTablePipeline();
1✔
1001

1002
        // Parse the JSON configuration
1003
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1004

1005
        // Load configuration into pipeline
1006
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1007
        REQUIRE(load_success);
1✔
1008

1009
        // Verify configuration was loaded correctly
1010
        auto table_configs = pipeline.getTableConfigurations();
1✔
1011
        REQUIRE(table_configs.size() == 1);
1✔
1012

1013
        auto const & config = table_configs[0];
1✔
1014
        REQUIRE(config.table_id == "interval_overlap_test");
1✔
1015
        REQUIRE(config.name == "Interval Overlap Test Table");
1✔
1016
        REQUIRE(config.columns.size() == 2);
1✔
1017

1018
        // Verify column configurations
1019
        auto const & column1 = config.columns[0];
1✔
1020
        REQUIRE(column1["name"] == "StimulusOverlapCount");
1✔
1021
        REQUIRE(column1["computer"] == "Interval Overlap Count");
1✔
1022
        REQUIRE(column1["data_source"] == "StimulusIntervals");
1✔
1023

1024
        auto const & column2 = config.columns[1];
1✔
1025
        REQUIRE(column2["name"] == "StimulusOverlapID");
1✔
1026
        REQUIRE(column2["computer"] == "Interval Overlap Assign ID");
1✔
1027
        REQUIRE(column2["data_source"] == "StimulusIntervals");
1✔
1028

1029
        // Verify row selector configuration
1030
        REQUIRE(config.row_selector["type"] == "interval");
1✔
1031
        REQUIRE(config.row_selector["source"] == "BehaviorPeriods");
1✔
1032

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

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

1041
        if (pipeline_result.success) {
1✔
1042
            std::cout << "Pipeline executed successfully!" << std::endl;
1✔
1043
            std::cout << "Tables completed: " << pipeline_result.tables_completed << "/" << pipeline_result.total_tables << std::endl;
1✔
1044
            std::cout << "Execution time: " << pipeline_result.total_execution_time_ms << " ms" << std::endl;
1✔
1045

1046
            // Verify the built table exists
1047
            auto & registry = getTableRegistry();
1✔
1048
            REQUIRE(registry.hasTable("interval_overlap_test"));
3✔
1049

1050
            // Get the built table and verify its structure
1051
            auto built_table = registry.getBuiltTable("interval_overlap_test");
3✔
1052
            REQUIRE(built_table != nullptr);
1✔
1053

1054
            // Check that the table has the expected columns
1055
            auto column_names = built_table->getColumnNames();
1✔
1056
            std::cout << "Built table has " << column_names.size() << " columns" << std::endl;
1✔
1057
            for (auto const & name: column_names) {
3✔
1058
                std::cout << "  Column: " << name << std::endl;
2✔
1059
            }
1060

1061
            REQUIRE(column_names.size() == 2);
1✔
1062
            REQUIRE(built_table->hasColumn("StimulusOverlapCount"));
3✔
1063
            REQUIRE(built_table->hasColumn("StimulusOverlapID"));
3✔
1064

1065
            // Verify table has 4 rows (one for each behavior period)
1066
            REQUIRE(built_table->getRowCount() == 4);
1✔
1067

1068
            // Get and verify the computed values
1069
            auto overlap_counts = built_table->getColumnValues<int64_t>("StimulusOverlapCount");
3✔
1070
            auto overlap_ids = built_table->getColumnValues<int64_t>("StimulusOverlapID");
3✔
1071

1072
            REQUIRE(overlap_counts.size() == 4);
1✔
1073
            REQUIRE(overlap_ids.size() == 4);
1✔
1074

1075
            for (size_t i = 0; i < 4; ++i) {
5✔
1076
                REQUIRE(overlap_counts[i] >= 0);
4✔
1077
                REQUIRE(overlap_counts[i] <= 4);// Cannot exceed total number of stimuli
4✔
1078
                REQUIRE(overlap_ids[i] >= -1);  // -1 means no overlap, >= 0 means valid ID
4✔
1079

1080
                std::cout << "Row " << i << ": Count=" << overlap_counts[i]
4✔
1081
                          << ", ID=" << overlap_ids[i] << std::endl;
4✔
1082
            }
1083

1084
        } else {
1✔
UNCOV
1085
            std::cout << "Pipeline execution failed: " << pipeline_result.error_message << std::endl;
×
1086
            // Now that we have proper fixtures and interval row selector implementation, this should succeed
UNCOV
1087
            FAIL("Pipeline execution failed: " + pipeline_result.error_message);
×
1088
        }
1089
    }
3✔
1090

1091
    SECTION("Test AssignID_Start and AssignID_End operations via JSON") {
2✔
1092
        char const * json_config = R"({
1✔
1093
            "metadata": {
1094
                "name": "Interval Overlap Start/End Test",
1095
                "description": "Test JSON execution of IntervalOverlapComputer start/end operations"
1096
            },
1097
            "tables": [
1098
                {
1099
                    "table_id": "interval_overlap_start_end_test",
1100
                    "name": "Interval Overlap Start/End Test Table",
1101
                    "description": "Test table using IntervalOverlapComputer start/end operations",
1102
                    "row_selector": {
1103
                        "type": "interval",
1104
                        "source": "BehaviorPeriods"
1105
                    },
1106
                    "columns": [
1107
                        {
1108
                            "name": "StimulusStartIndex",
1109
                            "description": "Start index of overlapping stimulus",
1110
                            "data_source": "StimulusIntervals",
1111
                            "computer": "Interval Overlap Assign Start"
1112
                        },
1113
                        {
1114
                            "name": "StimulusEndIndex",
1115
                            "description": "End index of overlapping stimulus",
1116
                            "data_source": "StimulusIntervals",
1117
                            "computer": "Interval Overlap Assign End"
1118
                        }
1119
                    ]
1120
                }
1121
            ]
1122
        })";
1123

1124
        auto & pipeline = getTablePipeline();
1✔
1125
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1126

1127
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1128
        REQUIRE(load_success);
1✔
1129

1130
        auto table_configs = pipeline.getTableConfigurations();
1✔
1131
        REQUIRE(table_configs.size() == 1);
1✔
1132

1133
        auto const & config = table_configs[0];
1✔
1134
        REQUIRE(config.columns.size() == 2);
1✔
1135
        REQUIRE(config.columns[0]["computer"] == "Interval Overlap Assign Start");
1✔
1136
        REQUIRE(config.columns[1]["computer"] == "Interval Overlap Assign End");
1✔
1137

1138
        std::cout << "Start/End operations JSON configuration parsed successfully" << std::endl;
1✔
1139

1140
        // Note: Full execution testing would depend on interval row selector implementation
1141
        // For now, we verify that the configuration is correctly parsed
1142
    }
3✔
1143
}
2✔
1144

1145
TEST_CASE_METHOD(IntervalTableRegistryTestFixture, "DM - TV - IntervalRowSelector implementation test", "[IntervalRowSelector][Pipeline]") {
2✔
1146

1147
    SECTION("Test interval row selector creation from source") {
2✔
1148
        // Simple JSON configuration to test interval row selector creation
1149
        char const * json_config = R"({
1✔
1150
            "metadata": {
1151
                "name": "Interval Row Selector Test",
1152
                "description": "Test interval row selector creation"
1153
            },
1154
            "tables": [
1155
                {
1156
                    "table_id": "interval_row_test",
1157
                    "name": "Interval Row Test Table",
1158
                    "description": "Test table with interval row selector",
1159
                    "row_selector": {
1160
                        "type": "interval",
1161
                        "source": "BehaviorPeriods"
1162
                    },
1163
                    "columns": [
1164
                        {
1165
                            "name": "StimulusCount",
1166
                            "description": "Count of overlapping stimuli",
1167
                            "data_source": "StimulusIntervals",
1168
                            "computer": "Interval Overlap Count"
1169
                        }
1170
                    ]
1171
                }
1172
            ]
1173
        })";
1174

1175
        auto & pipeline = getTablePipeline();
1✔
1176
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1177

1178
        // Load configuration
1179
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1180
        REQUIRE(load_success);
1✔
1181

1182
        // Execute the pipeline
1183
        auto pipeline_result = pipeline.execute();
1✔
1184

1185
        // Verify that the pipeline now executes successfully with our interval row selector implementation
1186
        if (pipeline_result.success) {
1✔
1187
            std::cout << "✓ Interval row selector pipeline executed successfully!" << std::endl;
1✔
1188

1189
            // Verify the built table exists and has correct structure
1190
            auto & registry = getTableRegistry();
1✔
1191
            REQUIRE(registry.hasTable("interval_row_test"));
3✔
1192

1193
            auto built_table = registry.getBuiltTable("interval_row_test");
3✔
1194
            REQUIRE(built_table != nullptr);
1✔
1195

1196
            // Should have 4 rows (one for each behavior period from our fixture)
1197
            REQUIRE(built_table->getRowCount() == 4);
1✔
1198
            REQUIRE(built_table->getColumnCount() == 1);
1✔
1199
            REQUIRE(built_table->hasColumn("StimulusCount"));
3✔
1200

1201
            auto counts = built_table->getColumnValues<int64_t>("StimulusCount");
3✔
1202
            REQUIRE(counts.size() == 4);
1✔
1203

1204
            // Verify all counts are reasonable
1205
            for (size_t i = 0; i < 4; ++i) {
5✔
1206
                REQUIRE(counts[i] >= 0);
4✔
1207
                REQUIRE(counts[i] <= 4);// Cannot exceed total number of stimuli
4✔
1208
                std::cout << "Behavior period " << i << ": " << counts[i] << " overlapping stimuli" << std::endl;
4✔
1209
            }
1210

1211
        } else {
1✔
UNCOV
1212
            FAIL("Pipeline execution failed: " + pipeline_result.error_message);
×
1213
        }
1214
    }
3✔
1215

1216
    SECTION("Test interval row selector with multiple operations") {
2✔
1217
        // More comprehensive test with multiple computers
1218
        char const * json_config = R"({
1✔
1219
            "metadata": {
1220
                "name": "Multi-Operation Interval Test",
1221
                "description": "Test multiple interval overlap operations"
1222
            },
1223
            "tables": [
1224
                {
1225
                    "table_id": "multi_interval_test",
1226
                    "name": "Multi Interval Test Table",
1227
                    "description": "Test table with multiple interval overlap operations",
1228
                    "row_selector": {
1229
                        "type": "interval",
1230
                        "source": "BehaviorPeriods"
1231
                    },
1232
                    "columns": [
1233
                        {
1234
                            "name": "OverlapCount",
1235
                            "description": "Count of overlapping stimuli",
1236
                            "data_source": "StimulusIntervals",
1237
                            "computer": "Interval Overlap Count"
1238
                        },
1239
                        {
1240
                            "name": "OverlapID",
1241
                            "description": "ID of overlapping stimulus",
1242
                            "data_source": "StimulusIntervals",
1243
                            "computer": "Interval Overlap Assign ID"
1244
                        },
1245
                        {
1246
                            "name": "OverlapStart",
1247
                            "description": "Start index of overlapping stimulus",
1248
                            "data_source": "StimulusIntervals",
1249
                            "computer": "Interval Overlap Assign Start"
1250
                        },
1251
                        {
1252
                            "name": "OverlapEnd",
1253
                            "description": "End index of overlapping stimulus",
1254
                            "data_source": "StimulusIntervals",
1255
                            "computer": "Interval Overlap Assign End"
1256
                        }
1257
                    ]
1258
                }
1259
            ]
1260
        })";
1261

1262
        auto & pipeline = getTablePipeline();
1✔
1263
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1264

1265
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1266
        REQUIRE(load_success);
1✔
1267

1268
        auto pipeline_result = pipeline.execute();
1✔
1269

1270
        if (pipeline_result.success) {
1✔
1271
            std::cout << "✓ Multi-operation interval pipeline executed successfully!" << std::endl;
1✔
1272

1273
            auto & registry = getTableRegistry();
1✔
1274
            auto built_table = registry.getBuiltTable("multi_interval_test");
3✔
1275
            REQUIRE(built_table != nullptr);
1✔
1276

1277
            REQUIRE(built_table->getRowCount() == 4);
1✔
1278
            REQUIRE(built_table->getColumnCount() == 4);
1✔
1279

1280
            // Verify all expected columns exist
1281
            REQUIRE(built_table->hasColumn("OverlapCount"));
3✔
1282
            REQUIRE(built_table->hasColumn("OverlapID"));
3✔
1283
            REQUIRE(built_table->hasColumn("OverlapStart"));
3✔
1284
            REQUIRE(built_table->hasColumn("OverlapEnd"));
3✔
1285

1286
            // Get all column data
1287
            auto counts = built_table->getColumnValues<int64_t>("OverlapCount");
3✔
1288
            auto ids = built_table->getColumnValues<int64_t>("OverlapID");
3✔
1289
            auto starts = built_table->getColumnValues<int64_t>("OverlapStart");
3✔
1290
            auto ends = built_table->getColumnValues<int64_t>("OverlapEnd");
3✔
1291

1292
            // Verify data consistency
1293
            for (size_t i = 0; i < 4; ++i) {
5✔
1294
                REQUIRE(counts[i] >= 0);
4✔
1295
                REQUIRE(ids[i] >= -1);   // -1 means no overlap
4✔
1296
                REQUIRE(starts[i] >= -1);// -1 means no overlap
4✔
1297
                REQUIRE(ends[i] >= -1);  // -1 means no overlap
4✔
1298

1299
                std::cout << "Row " << i << ": Count=" << counts[i]
4✔
1300
                          << ", ID=" << ids[i]
4✔
1301
                          << ", Start=" << starts[i]
4✔
1302
                          << ", End=" << ends[i] << std::endl;
4✔
1303
            }
1304

1305
        } else {
1✔
UNCOV
1306
            FAIL("Multi-operation pipeline execution failed: " + pipeline_result.error_message);
×
1307
        }
1308
    }
3✔
1309
}
2✔
1310

1311
TEST_CASE_METHOD(IntervalTableRegistryTestFixture, "DM - TV - IntervalOverlapComputer EntityID Round Trip", "[IntervalOverlapComputer][EntityID][TableView]") {
4✔
1312

1313
    SECTION("Test Simple EntityID structure for AssignID operations") {
4✔
1314
        auto & dm = getDataManager();
1✔
1315
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1316

1317
        // Get the interval sources from the DataManager
1318
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1319
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
1320

1321
        REQUIRE(behavior_source != nullptr);
1✔
1322
        REQUIRE(stimulus_source != nullptr);
1✔
1323

1324
        // Create row selector from behavior intervals
1325
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1326
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
1327
                TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
1328

1329
        REQUIRE(behavior_intervals.size() == 4);// 4 behavior periods
1✔
1330

1331
        // Convert to TimeFrameIntervals for row selector
1332
        std::vector<TimeFrameInterval> row_intervals;
1✔
1333
        for (auto const & interval: behavior_intervals) {
5✔
1334
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
1335
        }
1336

1337
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1338

1339
        // Create IntervalOverlapComputer for AssignID operation (Simple EntityID structure)
1340
        auto assign_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
1341
                stimulus_source,
1342
                IntervalOverlapOperation::AssignID,
1✔
1343
                "StimulusIntervals");
1✔
1344

1345
        // Verify EntityID structure is Simple for AssignID operations
1346
        REQUIRE(assign_computer->getEntityIdStructure() == EntityIdStructure::Simple);
1✔
1347
        REQUIRE(assign_computer->hasEntityIds());
1✔
1348

1349
        // Create TableView builder and add the column
1350
        TableViewBuilder builder(dme);
1✔
1351
        builder.setRowSelector(std::move(row_selector));
1✔
1352
        builder.addColumn<int64_t>("StimulusAssignID", std::move(assign_computer));
3✔
1353

1354
        // Build the table
1355
        TableView table = builder.build();
1✔
1356

1357
        auto assign_id_data_from_table = table.getColumnValues<int64_t>("StimulusAssignID");
3✔
1358

1359
        // Verify table structure
1360
        REQUIRE(table.getRowCount() == 4);
1✔
1361
        REQUIRE(table.getColumnCount() == 1);
1✔
1362
        REQUIRE(table.hasColumn("StimulusAssignID"));
3✔
1363

1364

1365
        // Test column-level EntityIDs using variant interface
1366
        ColumnEntityIds column_entity_ids = table.getColumnEntityIds("StimulusAssignID");
3✔
1367
        REQUIRE(std::holds_alternative<std::vector<EntityId>>(column_entity_ids));
1✔
1368

1369
        auto simple_entity_ids = std::get<std::vector<EntityId>>(column_entity_ids);
1✔
1370
        REQUIRE(simple_entity_ids.size() == 4);// One EntityID per row
1✔
1371

1372
        // Test cell-level EntityID extraction
1373
        for (size_t row = 0; row < 4; ++row) {
5✔
1374
            std::vector<EntityId> cell_entity_ids = table.getCellEntityIds("StimulusAssignID", row);
12✔
1375
            REQUIRE(cell_entity_ids.size() == 1);// AssignID gives single EntityID per cell
4✔
1376
            REQUIRE(cell_entity_ids[0] == simple_entity_ids[row]);
4✔
1377
        }
4✔
1378

1379
        std::cout << "✓ Simple EntityID structure test passed for AssignID operations" << std::endl;
1✔
1380
        std::cout << "  - Column EntityIDs: " << simple_entity_ids.size() << " entries" << std::endl;
1✔
1381
        std::cout << "  - All EntityIDs are valid and non-empty" << std::endl;
1✔
1382
    }
5✔
1383

1384
    SECTION("Test Complex EntityID structure for CountOverlaps operations") {
4✔
1385
        auto & dm = getDataManager();
1✔
1386
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1387

1388
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1389
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
1390

1391
        REQUIRE(behavior_source != nullptr);
1✔
1392
        REQUIRE(stimulus_source != nullptr);
1✔
1393

1394
        // Create row selector from behavior intervals
1395
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1396
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
1397
                TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
1398

1399
        std::vector<TimeFrameInterval> row_intervals;
1✔
1400
        for (auto const & interval: behavior_intervals) {
5✔
1401
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
1402
        }
1403

1404
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1405

1406
        // Create IntervalOverlapComputer for CountOverlaps operation (Complex EntityID structure)
1407
        auto count_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
1408
                stimulus_source,
1409
                IntervalOverlapOperation::CountOverlaps,
1✔
1410
                "StimulusIntervals");
1✔
1411

1412
        // Verify EntityID structure is Complex for CountOverlaps operations
1413
        REQUIRE(count_computer->getEntityIdStructure() == EntityIdStructure::Complex);
1✔
1414
        REQUIRE(count_computer->hasEntityIds());
1✔
1415

1416
        // Create TableView builder and add the column
1417
        TableViewBuilder builder(dme);
1✔
1418
        builder.setRowSelector(std::move(row_selector));
1✔
1419
        builder.addColumn<int64_t>("StimulusCount", std::move(count_computer));
3✔
1420

1421
        // Build the table
1422
        TableView table = builder.build();
1✔
1423

1424
        auto count_data_from_table = table.getColumnValues<int64_t>("StimulusCount");
3✔
1425

1426
        // Verify table structure
1427
        REQUIRE(table.getRowCount() == 4);
1✔
1428
        REQUIRE(table.getColumnCount() == 1);
1✔
1429
        REQUIRE(table.hasColumn("StimulusCount"));
3✔
1430

1431

1432
        // Test column-level EntityIDs using variant interface
1433
        ColumnEntityIds column_entity_ids = table.getColumnEntityIds("StimulusCount");
3✔
1434
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(column_entity_ids));
1✔
1435

1436
        auto complex_entity_ids = std::get<std::vector<std::vector<EntityId>>>(column_entity_ids);
1✔
1437
        REQUIRE(complex_entity_ids.size() == 4);// One vector of EntityIDs per row
1✔
1438

1439
        // Test cell-level EntityID extraction
1440
        for (size_t row = 0; row < 4; ++row) {
5✔
1441
            std::vector<EntityId> cell_entity_ids = table.getCellEntityIds("StimulusCount", row);
12✔
1442
            REQUIRE(cell_entity_ids == complex_entity_ids[row]);
4✔
1443

1444
            // Each row should have zero or more EntityIDs (depending on overlaps)
1445
            REQUIRE(cell_entity_ids.size() >= 0);
4✔
1446
        }
4✔
1447

1448
        std::cout << "✓ Complex EntityID structure test passed for CountOverlaps operations" << std::endl;
1✔
1449
        std::cout << "  - Column EntityIDs: " << complex_entity_ids.size() << " rows" << std::endl;
1✔
1450
        for (size_t i = 0; i < complex_entity_ids.size(); ++i) {
5✔
1451
            std::cout << "    Row " << i << ": " << complex_entity_ids[i].size() << " EntityIDs" << std::endl;
4✔
1452
        }
1453
    }
5✔
1454

1455
    SECTION("Test EntityID variant interface consistency") {
4✔
1456
        auto & dm = getDataManager();
1✔
1457
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1458

1459
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1460
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
1461

1462
        // Create row selector from behavior intervals
1463
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1464
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
1465
                TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
1466

1467
        std::vector<TimeFrameInterval> row_intervals;
1✔
1468
        for (auto const & interval: behavior_intervals) {
5✔
1469
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
1470
        }
1471

1472
        auto row_selector1 = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1473
        auto row_selector2 = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1474

1475
        // Create both types of computers
1476
        auto assign_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
1477
                stimulus_source, IntervalOverlapOperation::AssignID, "StimulusIntervals");
1✔
1478
        auto count_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
1479
                stimulus_source, IntervalOverlapOperation::CountOverlaps, "StimulusIntervals");
1✔
1480

1481
        // Create two separate tables to test both
1482
        TableViewBuilder builder1(dme);
1✔
1483
        builder1.setRowSelector(std::move(row_selector1));
1✔
1484
        builder1.addColumn<int64_t>("AssignColumn", std::move(assign_computer));
3✔
1485

1486
        TableViewBuilder builder2(dme);
1✔
1487
        builder2.setRowSelector(std::move(row_selector2));
1✔
1488
        builder2.addColumn<int64_t>("CountColumn", std::move(count_computer));
3✔
1489

1490
        TableView assign_table = builder1.build();
1✔
1491
        TableView count_table = builder2.build();
1✔
1492

1493
        auto assign_data_from_table = assign_table.getColumnValues<int64_t>("AssignColumn");
3✔
1494
        auto count_data_from_table = count_table.getColumnValues<int64_t>("CountColumn");
3✔
1495

1496
        // Test variant types
1497
        ColumnEntityIds assign_ids = assign_table.getColumnEntityIds("AssignColumn");
3✔
1498
        ColumnEntityIds count_ids = count_table.getColumnEntityIds("CountColumn");
3✔
1499

1500
        REQUIRE(std::holds_alternative<std::vector<EntityId>>(assign_ids));
1✔
1501
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(count_ids));
1✔
1502

1503
        // Verify both variants work correctly
1504
        auto simple_ids = std::get<std::vector<EntityId>>(assign_ids);
1✔
1505
        auto complex_ids = std::get<std::vector<std::vector<EntityId>>>(count_ids);
1✔
1506

1507
        REQUIRE(simple_ids.size() == 4);
1✔
1508
        REQUIRE(complex_ids.size() == 4);
1✔
1509

1510
        // Test cell-level consistency
1511
        for (size_t row = 0; row < 4; ++row) {
5✔
1512
            auto assign_cell = assign_table.getCellEntityIds("AssignColumn", row);
12✔
1513
            auto count_cell = count_table.getCellEntityIds("CountColumn", row);
12✔
1514

1515
            // AssignID should return single EntityID
1516
            REQUIRE(assign_cell.size() == 1);
4✔
1517
            REQUIRE(assign_cell[0] == simple_ids[row]);
4✔
1518

1519
            // CountOverlaps should return multiple EntityIDs (may be same as assign but in vector)
1520
            REQUIRE(count_cell == complex_ids[row]);
4✔
1521
        }
4✔
1522

1523
        std::cout << "✓ Variant interface consistency test passed" << std::endl;
1✔
1524
        std::cout << "  - Simple structure: " << simple_ids.size() << " EntityIDs" << std::endl;
1✔
1525
        std::cout << "  - Complex structure: " << complex_ids.size() << " rows of EntityIDs" << std::endl;
1✔
1526
    }
5✔
1527

1528
    SECTION("Test EntityID round trip with source data verification") {
4✔
1529
        auto & dm = getDataManager();
1✔
1530
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1531

1532
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1533
        auto stimulus_source = dme->getIntervalSource("StimulusIntervals");
3✔
1534

1535
        REQUIRE(behavior_source != nullptr);
1✔
1536
        REQUIRE(stimulus_source != nullptr);
1✔
1537

1538
        // Get original source EntityIDs
1539
        auto stimulus_time_frame = dm.getTime(TimeKey("stimulus_time"));
1✔
1540
        auto original_stimulus_intervals = stimulus_source->getIntervalsInRange(
1✔
1541
                TimeFrameIndex(0), TimeFrameIndex(20), stimulus_time_frame.get());
1✔
1542

1543
        std::cout << "Source data has " << original_stimulus_intervals.size() << " stimulus intervals" << std::endl;
1✔
1544

1545
        // Create row selector from behavior intervals
1546
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1547
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
1548
                TimeFrameIndex(0), TimeFrameIndex(100), behavior_time_frame.get());
1✔
1549

1550
        std::vector<TimeFrameInterval> row_intervals;
1✔
1551
        for (auto const & interval: behavior_intervals) {
5✔
1552
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
1553
        }
1554

1555
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
1556

1557
        // Create CountOverlaps computer to get all overlapping EntityIDs
1558
        auto count_computer = std::make_unique<IntervalOverlapComputer<int64_t>>(
1✔
1559
                stimulus_source,
1560
                IntervalOverlapOperation::CountOverlaps,
1✔
1561
                "StimulusIntervals");
1✔
1562

1563
        // Create table with the computer
1564
        TableViewBuilder builder(dme);
1✔
1565
        builder.setRowSelector(std::move(row_selector));
1✔
1566
        builder.addColumn<int64_t>("StimulusOverlaps", std::move(count_computer));
3✔
1567

1568
        TableView table = builder.build();
1✔
1569

1570
        auto count_data_from_table = table.getColumnValues<int64_t>("StimulusOverlaps");
3✔
1571

1572
        // Get all EntityIDs from the column
1573
        ColumnEntityIds column_entity_ids = table.getColumnEntityIds("StimulusOverlaps");
3✔
1574
        REQUIRE(std::holds_alternative<std::vector<std::vector<EntityId>>>(column_entity_ids));
1✔
1575

1576
        auto complex_entity_ids = std::get<std::vector<std::vector<EntityId>>>(column_entity_ids);
1✔
1577

1578
        // Collect all unique EntityIDs from the table
1579
        std::set<EntityId> table_entity_ids;
1✔
1580
        for (auto const & row_entity_ids: complex_entity_ids) {
5✔
1581
            for (auto const & entity_id: row_entity_ids) {
8✔
1582
                table_entity_ids.insert(entity_id);
4✔
1583
            }
1584
        }
1585

1586
        std::cout << "Table extracted " << table_entity_ids.size() << " unique EntityIDs" << std::endl;
1✔
1587

1588
        // Get original EntityIDs directly from the source data for verification
1589
        auto stimulus_data = dm.getData<DigitalIntervalSeries>("StimulusIntervals");
3✔
1590
        REQUIRE(stimulus_data != nullptr);
1✔
1591

1592
        auto source_stimulus_entity_ids = stimulus_data->getEntityIds();
1✔
1593
        std::cout << "Source stimulus data has " << source_stimulus_entity_ids.size() << " EntityIDs" << std::endl;
1✔
1594

1595
        // Debug: Print source EntityIDs
1596
        INFO("Source EntityIDs from StimulusIntervals:");
1✔
1597
        for (size_t i = 0; i < source_stimulus_entity_ids.size(); ++i) {
4✔
1598
            INFO("  Source EntityID[" << i << "] = " << source_stimulus_entity_ids[i]);
3✔
1599
        }
3✔
1600

1601
        // Debug: Print table EntityIDs
1602
        INFO("Table EntityIDs from IntervalOverlapComputer:");
1✔
1603
        for (auto const & entity_id: table_entity_ids) {
4✔
1604
            INFO("  Table EntityID = " << entity_id);
3✔
1605
        }
3✔
1606

1607
        // Verify that extracted EntityIDs are a subset of source EntityIDs
1608
        // (Not all source EntityIDs may appear in the table due to overlap filtering)
1609
        for (auto const & table_entity_id: table_entity_ids) {
4✔
1610
            bool found = std::find(source_stimulus_entity_ids.begin(),
3✔
1611
                                   source_stimulus_entity_ids.end(),
1612
                                   table_entity_id) != source_stimulus_entity_ids.end();
6✔
1613
            REQUIRE(found);
3✔
1614
            INFO("✓ Table EntityID " << table_entity_id << " found in source data");
3✔
1615
        }
3✔
1616

1617
        // Verify all EntityIDs are valid (non-zero)
1618
        for (auto const & entity_id: table_entity_ids) {
4✔
1619
            REQUIRE(entity_id != 0);
3✔
1620
        }
1621

1622
        // Verify cell-level EntityIDs match column-level EntityIDs
1623
        for (size_t row = 0; row < table.getRowCount(); ++row) {
5✔
1624
            auto cell_entity_ids = table.getCellEntityIds("StimulusOverlaps", row);
12✔
1625
            REQUIRE(cell_entity_ids == complex_entity_ids[row]);
4✔
1626
        }
4✔
1627

1628
        std::cout << "✓ EntityID round trip test passed" << std::endl;
1✔
1629
        std::cout << "  - All EntityIDs are valid and come from source data" << std::endl;
1✔
1630
        std::cout << "  - Cell-level extraction matches column-level extraction" << std::endl;
1✔
1631
        std::cout << "  - Extracted EntityIDs verified against original source data" << std::endl;
1✔
1632
    }
5✔
1633
}
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