• 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

94.4
/src/DataManager/utils/TableView/computers/IntervalPropertyComputer.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 "DataManager.hpp"
6
#include "DigitalTimeSeries/Digital_Interval_Series.hpp"
7
#include "Entity/EntityGroupManager.hpp"
8
#include "IntervalPropertyComputer.h"
9
#include "TimeFrame/TimeFrame.hpp"
10
#include "TimeFrame/interval_data.hpp"
11
#include "utils/TableView/ComputerRegistry.hpp"
12
#include "utils/TableView/TableRegistry.hpp"
13
#include "utils/TableView/adapters/DataManagerExtension.h"
14
#include "utils/TableView/core/ExecutionPlan.h"
15
#include "utils/TableView/core/TableView.h"
16
#include "utils/TableView/core/TableViewBuilder.h"
17
#include "utils/TableView/interfaces/IIntervalSource.h"
18
#include "utils/TableView/interfaces/IRowSelector.h"
19
#include "utils/TableView/pipeline/TablePipeline.hpp"
20

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

27
/**
28
 * @brief Base test fixture for IntervalPropertyComputer with realistic interval data
29
 * 
30
 * This fixture provides a DataManager populated with:
31
 * - TimeFrames with different granularities
32
 * - Intervals representing different event periods with realistic durations
33
 * - Cross-timeframe scenarios for comprehensive testing
34
 */
35
class IntervalPropertyTestFixture {
36
protected:
37
    IntervalPropertyTestFixture() {
8✔
38
        // Initialize the DataManager
39
        m_data_manager = std::make_unique<DataManager>();
8✔
40

41
        // Populate with test data
42
        populateWithIntervalTestData();
8✔
43
    }
8✔
44

45
    ~IntervalPropertyTestFixture() = default;
8✔
46

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

54
private:
55
    std::unique_ptr<DataManager> m_data_manager;
56

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

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

77
        // Create "neural_time" timeframe: 0, 2, 4, 6, ..., 200 (101 points) - neural recording at 5Hz
78
        std::vector<int> neural_time_values;
8✔
79
        neural_time_values.reserve(101);
8✔
80
        for (int i = 0; i <= 100; ++i) {
816✔
81
            neural_time_values.push_back(i * 2);
808✔
82
        }
83
        auto neural_time_frame = std::make_shared<TimeFrame>(neural_time_values);
8✔
84
        m_data_manager->setTime(TimeKey("neural_time"), neural_time_frame, true);
8✔
85

86
        // Create "high_res_time" timeframe: millisecond resolution for precise timing
87
        std::vector<int> high_res_values;
8✔
88
        high_res_values.reserve(201);
8✔
89
        for (int i = 0; i <= 200; ++i) {
1,616✔
90
            high_res_values.push_back(i);
1,608✔
91
        }
92
        auto high_res_frame = std::make_shared<TimeFrame>(high_res_values);
8✔
93
        m_data_manager->setTime(TimeKey("high_res_time"), high_res_frame, true);
8✔
94
    }
16✔
95

96
    /**
97
     * @brief Create behavior intervals for testing property extraction
98
     */
99
    void createBehaviorIntervals() {
8✔
100
        // Create behavior periods with varying durations
101
        auto behavior_intervals = std::make_shared<DigitalIntervalSeries>();
8✔
102
        behavior_intervals->setIdentityContext("BehaviorPeriods", m_data_manager->getEntityRegistry());
24✔
103

104
        // Short grooming bout: time 10-15 (duration = 5)
105
        behavior_intervals->addEvent(TimeFrameIndex(10), TimeFrameIndex(15));
8✔
106

107
        // Medium exploration period: time 30-50 (duration = 20)
108
        behavior_intervals->addEvent(TimeFrameIndex(30), TimeFrameIndex(50));
8✔
109

110
        // Long rest period: time 70-120 (duration = 50)
111
        behavior_intervals->addEvent(TimeFrameIndex(70), TimeFrameIndex(120));
8✔
112

113
        // Brief social interaction: time 150-155 (duration = 5)
114
        behavior_intervals->addEvent(TimeFrameIndex(150), TimeFrameIndex(155));
8✔
115

116
        // Very long sleep period: time 160-190 (duration = 30)
117
        behavior_intervals->addEvent(TimeFrameIndex(160), TimeFrameIndex(190));
8✔
118

119
        // Rebuild entity IDs to ensure they're generated
120
        behavior_intervals->rebuildAllEntityIds();
8✔
121

122
        m_data_manager->setData<DigitalIntervalSeries>("BehaviorPeriods", behavior_intervals, TimeKey("behavior_time"));
24✔
123
    }
16✔
124

125
    /**
126
     * @brief Create stimulus intervals with different patterns
127
     */
128
    void createStimulusIntervals() {
8✔
129
        // Create stimulus presentation periods with precise timing
130
        auto stimulus_intervals = std::make_shared<DigitalIntervalSeries>();
8✔
131
        stimulus_intervals->setIdentityContext("StimulusIntervals", m_data_manager->getEntityRegistry());
24✔
132

133
        // Brief stimulus 1: time 5-8 (duration = 3)
134
        stimulus_intervals->addEvent(TimeFrameIndex(5), TimeFrameIndex(8));
8✔
135

136
        // Medium stimulus 2: time 25-35 (duration = 10)
137
        stimulus_intervals->addEvent(TimeFrameIndex(25), TimeFrameIndex(35));
8✔
138

139
        // Long stimulus 3: time 80-100 (duration = 20)
140
        stimulus_intervals->addEvent(TimeFrameIndex(80), TimeFrameIndex(100));
8✔
141

142
        // Short stimulus 4: time 140-142 (duration = 2)
143
        stimulus_intervals->addEvent(TimeFrameIndex(140), TimeFrameIndex(142));
8✔
144

145
        // Rebuild entity IDs to ensure they're generated
146
        stimulus_intervals->rebuildAllEntityIds();
8✔
147

148
        m_data_manager->setData<DigitalIntervalSeries>("StimulusIntervals", stimulus_intervals, TimeKey("neural_time"));
24✔
149
    }
16✔
150

151
    /**
152
     * @brief Create neural event intervals for high-resolution testing
153
     */
154
    void createNeuralEvents() {
8✔
155
        // Create neural activity bursts with precise timing
156
        auto neural_intervals = std::make_shared<DigitalIntervalSeries>();
8✔
157
        neural_intervals->setIdentityContext("NeuralEvents", m_data_manager->getEntityRegistry());
24✔
158

159
        // Microsecond-level precision events
160
        // Event 1: time 12-13 (duration = 1)
161
        neural_intervals->addEvent(TimeFrameIndex(12), TimeFrameIndex(13));
8✔
162

163
        // Event 2: time 45-47 (duration = 2)
164
        neural_intervals->addEvent(TimeFrameIndex(45), TimeFrameIndex(47));
8✔
165

166
        // Event 3: time 89-92 (duration = 3)
167
        neural_intervals->addEvent(TimeFrameIndex(89), TimeFrameIndex(92));
8✔
168

169
        // Event 4: time 156-161 (duration = 5)
170
        neural_intervals->addEvent(TimeFrameIndex(156), TimeFrameIndex(161));
8✔
171

172
        // Rebuild entity IDs to ensure they're generated
173
        neural_intervals->rebuildAllEntityIds();
8✔
174

175
        m_data_manager->setData<DigitalIntervalSeries>("NeuralEvents", neural_intervals, TimeKey("high_res_time"));
24✔
176
    }
16✔
177
};
178

179
/**
180
 * @brief Test fixture combining IntervalPropertyTestFixture with TableRegistry and TablePipeline
181
 * 
182
 * This fixture provides everything needed to test JSON-based table pipeline execution:
183
 * - DataManager with interval test data (from IntervalPropertyTestFixture)
184
 * - TableRegistry for managing table configurations
185
 * - TablePipeline for executing JSON configurations
186
 */
187
class IntervalPropertyTableRegistryTestFixture : public IntervalPropertyTestFixture {
188
protected:
189
    IntervalPropertyTableRegistryTestFixture()
5✔
190
        : IntervalPropertyTestFixture() {
5✔
191
        // Use the DataManager's existing TableRegistry instead of creating a new one
192
        m_table_registry_ptr = getDataManager().getTableRegistry();
5✔
193

194
        // Initialize TablePipeline with the existing TableRegistry
195
        m_table_pipeline = std::make_unique<TablePipeline>(m_table_registry_ptr, &getDataManager());
5✔
196
    }
5✔
197

198
    ~IntervalPropertyTableRegistryTestFixture() = default;
5✔
199

200
    /**
201
     * @brief Get the TableRegistry instance
202
     * @return Reference to the TableRegistry
203
     */
204
    TableRegistry & getTableRegistry() { return *m_table_registry_ptr; }
5✔
205

206
    /**
207
     * @brief Get the TableRegistry instance (const version)
208
     * @return Const reference to the TableRegistry
209
     */
210
    TableRegistry const & getTableRegistry() const { return *m_table_registry_ptr; }
211

212
    /**
213
     * @brief Get a pointer to the TableRegistry
214
     * @return Raw pointer to the TableRegistry
215
     */
216
    TableRegistry * getTableRegistryPtr() { return m_table_registry_ptr; }
217

218
    /**
219
     * @brief Get the TablePipeline instance
220
     * @return Reference to the TablePipeline
221
     */
222
    TablePipeline & getTablePipeline() { return *m_table_pipeline; }
2✔
223

224
    /**
225
     * @brief Get the TablePipeline instance (const version)
226
     * @return Const reference to the TablePipeline
227
     */
228
    TablePipeline const & getTablePipeline() const { return *m_table_pipeline; }
229

230
    /**
231
     * @brief Get a pointer to the TablePipeline
232
     * @return Raw pointer to the TablePipeline
233
     */
234
    TablePipeline * getTablePipelinePtr() { return m_table_pipeline.get(); }
235

236
    /**
237
     * @brief Get the DataManagerExtension instance
238
     */
239
    std::shared_ptr<DataManagerExtension> getDataManagerExtension() {
240
        if (!m_data_manager_extension) {
241
            m_data_manager_extension = std::make_shared<DataManagerExtension>(getDataManager());
242
        }
243
        return m_data_manager_extension;
244
    }
245

246
private:
247
    TableRegistry * m_table_registry_ptr;// Points to DataManager's TableRegistry
248
    std::unique_ptr<TablePipeline> m_table_pipeline;
249
    std::shared_ptr<DataManagerExtension> m_data_manager_extension;// Lazy-initialized
250
};
251

252
// Mock implementation of IIntervalSource for testing
253
class MockIntervalSource : public IIntervalSource {
254
public:
255
    MockIntervalSource(std::string name,
×
256
                       std::shared_ptr<TimeFrame> timeFrame,
257
                       std::vector<Interval> intervals)
258
        : m_name(std::move(name)),
×
259
          m_timeFrame(std::move(timeFrame)),
×
260
          m_intervals(std::move(intervals)) {}
×
261

262
    [[nodiscard]] auto getName() const -> std::string const & override {
×
263
        return m_name;
×
264
    }
265

266
    [[nodiscard]] auto getTimeFrame() const -> std::shared_ptr<TimeFrame> override {
×
267
        return m_timeFrame;
×
268
    }
269

270
    [[nodiscard]] auto size() const -> size_t override {
×
271
        return m_intervals.size();
×
272
    }
273

274
    auto getIntervals() -> std::vector<Interval> override {
×
275
        return m_intervals;
×
276
    }
277

278
    auto getIntervalsInRange(TimeFrameIndex start, TimeFrameIndex end,
×
279
                             TimeFrame const * target_timeFrame) -> std::vector<Interval> override {
280
        std::vector<Interval> result;
×
281

282
        // Convert TimeFrameIndex to time values for comparison
283
        auto startTime = target_timeFrame->getTimeAtIndex(start);
×
284
        auto endTime = target_timeFrame->getTimeAtIndex(end);
×
285

286
        for (auto const & interval: m_intervals) {
×
287
            // Convert interval indices to time values using our timeframe
288
            auto intervalStartTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.start));
×
289
            auto intervalEndTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.end));
×
290

291
            // Check if intervals overlap in time
292
            if (intervalStartTime <= endTime && startTime <= intervalEndTime) {
×
293
                result.push_back(interval);
×
294
            }
295
        }
296

297
        return result;
×
298
    }
×
299

NEW
300
    auto getIntervalsWithIdsInRange(TimeFrameIndex start, TimeFrameIndex end,
×
301
                                    TimeFrame const * target_timeFrame) -> std::vector<IntervalWithId> override {
UNCOV
302
        std::vector<IntervalWithId> result;
×
303

304
        // Convert TimeFrameIndex to time values for comparison
NEW
305
        auto startTime = target_timeFrame->getTimeAtIndex(start);
×
NEW
306
        auto endTime = target_timeFrame->getTimeAtIndex(end);
×
307

NEW
308
        for (auto const & interval: m_intervals) {
×
309
            // Convert interval indices to time values using our timeframe
NEW
310
            auto intervalStartTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.start));
×
NEW
311
            auto intervalEndTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.end));
×
312

313
            // Check if intervals overlap in time
NEW
314
            if (intervalStartTime <= endTime && startTime <= intervalEndTime) {
×
NEW
315
                result.push_back(IntervalWithId(interval, 0));
×
316
            }
317
        }
318

UNCOV
319
        return result;
×
UNCOV
320
    }
×
321

322
private:
323
    std::string m_name;
324
    std::shared_ptr<TimeFrame> m_timeFrame;
325
    std::vector<Interval> m_intervals;
326
};
327

328
TEST_CASE("DM - TV - IntervalPropertyComputer Basic Functionality", "[IntervalPropertyComputer]") {
5✔
329

330
    SECTION("Start property extraction with matching intervals") {
5✔
331
        // Create time frame
332
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
3✔
333
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
334

335
        // Create source intervals (non-overlapping, properly ordered)
336
        std::vector<Interval> sourceIntervals = {
1✔
337
            {0, 2},   // Start = 0
338
            {4, 6},   // Start = 4  
339
            {8, 10},  // Start = 8
340
            {12, 14}  // Start = 12
341
        };
3✔
342

343
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
344
            "TestIntervals", timeFrame, sourceIntervals);
1✔
345

346
        // Create row intervals that match the source intervals exactly
347
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
348
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2)), // Start = 0
349
                TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(6)), // Start = 4
350
                TimeFrameInterval(TimeFrameIndex(8), TimeFrameIndex(10)), // Start = 8
351
                TimeFrameInterval(TimeFrameIndex(12), TimeFrameIndex(14)) // Start = 12
352
        };
3✔
353

354
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
355

356
        // Create the computer for Start property
357
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
358
                                                   IntervalProperty::Start,
359
                                                   "TestIntervals");
3✔
360

361
        // Compute the results
362
        auto [results, entity_ids] = computer.compute(plan);
1✔
363

364
        // Verify results
365
        REQUIRE(results.size() == 4);
1✔
366
        REQUIRE(results[0] == 0);// First interval start
1✔
367
        REQUIRE(results[1] == 4);// Second interval start
1✔
368
        REQUIRE(results[2] == 8);// Third interval start
1✔
369
        REQUIRE(results[3] == 12);// Fourth interval start
1✔
370
    }
6✔
371

372
    SECTION("End property extraction with matching intervals") {
5✔
373
        // Create time frame
374
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
3✔
375
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
376

377
        // Create source intervals (non-overlapping, properly ordered)
378
        std::vector<Interval> sourceIntervals = {
1✔
379
            {0, 2},   // End = 2
380
            {4, 6},   // End = 6
381
            {8, 10},  // End = 10
382
            {12, 14}  // End = 14
383
        };
3✔
384
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
385
                "TestIntervals", timeFrame, sourceIntervals);
1✔
386

387
        // Create row intervals that match the source intervals exactly
388
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
389
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2)), // End = 2
390
                TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(6)), // End = 6
391
                TimeFrameInterval(TimeFrameIndex(8), TimeFrameIndex(10)), // End = 10
392
                TimeFrameInterval(TimeFrameIndex(12), TimeFrameIndex(14)) // End = 14
393
        };
3✔
394

395
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
396

397
        // Create the computer for End property
398
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
399
                                                   IntervalProperty::End,
400
                                                   "TestIntervals");
3✔
401

402
        // Compute the results
403
        auto [results, entity_ids] = computer.compute(plan);
1✔
404

405
        // Verify results
406
        REQUIRE(results.size() == 4);
1✔
407
        REQUIRE(results[0] == 2);// First interval end
1✔
408
        REQUIRE(results[1] == 6);// Second interval end
1✔
409
        REQUIRE(results[2] == 10);// Third interval end
1✔
410
        REQUIRE(results[3] == 14);// Fourth interval end
1✔
411
    }
6✔
412

413
    SECTION("Duration property extraction with matching intervals") {
5✔
414
        // Create time frame
415
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
3✔
416
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
417

418
        // Create source intervals (non-overlapping, properly ordered)
419
        std::vector<Interval> sourceIntervals = {
1✔
420
            {0, 2},   // Duration = 2-0 = 2
421
            {4, 7},   // Duration = 7-4 = 3
422
            {8, 10},  // Duration = 10-8 = 2
423
            {12, 15}  // Duration = 15-12 = 3
424
        };
3✔
425
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
426
                "TestIntervals", timeFrame, sourceIntervals);
1✔
427

428
        // Create row intervals that match the source intervals exactly
429
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
430
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(2)), // Duration = 2-0 = 2
431
                TimeFrameInterval(TimeFrameIndex(4), TimeFrameIndex(7)), // Duration = 7-4 = 3
432
                TimeFrameInterval(TimeFrameIndex(8), TimeFrameIndex(10)), // Duration = 10-8 = 2
433
                TimeFrameInterval(TimeFrameIndex(12), TimeFrameIndex(15)) // Duration = 15-12 = 3
434
        };
3✔
435

436
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
437

438
        // Create the computer for Duration property
439
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
440
                                                   IntervalProperty::Duration,
441
                                                   "TestIntervals");
3✔
442

443
        // Compute the results
444
        auto [results, entity_ids] = computer.compute(plan);
1✔
445

446
        // Verify results
447
        REQUIRE(results.size() == 4);
1✔
448
        REQUIRE(results[0] == 2);// Duration = 2-0
1✔
449
        REQUIRE(results[1] == 3);// Duration = 7-4
1✔
450
        REQUIRE(results[2] == 2);// Duration = 10-8
1✔
451
        REQUIRE(results[3] == 3);// Duration = 15-12
1✔
452
    }
6✔
453

454
    SECTION("Single interval scenarios") {
5✔
455
        // Create time frame
456
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
457
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
458

459
        // Create source intervals that match the row intervals
460
        std::vector<Interval> sourceIntervals = {{2, 4}}; // Start=2, End=4, Duration=2
3✔
461
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
462
                "SingleInterval", timeFrame, sourceIntervals);
1✔
463

464
        // Create single row interval that matches the source interval
465
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
466
                TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(4))// Start=2, End=4, Duration=2
467
        };
3✔
468

469
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
470

471
        // Test Start property
472
        IntervalPropertyComputer<int64_t> startComputer(intervalSource,
1✔
473
                                                        IntervalProperty::Start,
474
                                                        "SingleInterval");
3✔
475
        auto [startResults, entity_ids] = startComputer.compute(plan);
1✔
476
        REQUIRE(startResults.size() == 1);
1✔
477
        REQUIRE(startResults[0] == 2);
1✔
478

479
        // Test End property
480
        IntervalPropertyComputer<int64_t> endComputer(intervalSource,
1✔
481
                                                      IntervalProperty::End,
482
                                                      "SingleInterval");
3✔
483
        auto [endResults, endEntity_ids] = endComputer.compute(plan);
1✔
484
        REQUIRE(endResults.size() == 1);
1✔
485
        REQUIRE(endResults[0] == 4);
1✔
486

487
        // Test Duration property
488
        IntervalPropertyComputer<int64_t> durationComputer(intervalSource,
1✔
489
                                                           IntervalProperty::Duration,
490
                                                           "SingleInterval");
3✔
491
        auto [durationResults, durationEntity_ids] = durationComputer.compute(plan);
1✔
492
        REQUIRE(durationResults.size() == 1);
1✔
493
        REQUIRE(durationResults[0] == 2);
1✔
494
    }
6✔
495

496
    SECTION("Zero-duration intervals with matching source") {
5✔
497
        // Create time frame
498
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
499
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
500

501
        // Create source intervals with zero-duration intervals
502
        std::vector<Interval> sourceIntervals = {
1✔
503
            {1, 1},  // Duration = 0
504
            {3, 3}   // Duration = 0
505
        };
3✔
506
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
507
                "ZeroDuration", timeFrame, sourceIntervals);
1✔
508

509
        // Create row intervals that match the source intervals exactly
510
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
511
                TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(1)),// Duration = 0
512
                TimeFrameInterval(TimeFrameIndex(3), TimeFrameIndex(3)) // Duration = 0
513
        };
3✔
514

515
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
516

517
        // Test Duration property
518
        IntervalPropertyComputer<int64_t> durationComputer(intervalSource,
1✔
519
                                                           IntervalProperty::Duration,
520
                                                           "ZeroDuration");
3✔
521
        auto [durationResults, entity_ids] = durationComputer.compute(plan);
1✔
522

523
        REQUIRE(durationResults.size() == 2);
1✔
524
        REQUIRE(durationResults[0] == 0);// Zero duration
1✔
525
        REQUIRE(durationResults[1] == 0);// Zero duration
1✔
526
    }
6✔
527
}
5✔
528

529
TEST_CASE("DM - TV - IntervalPropertyComputer Error Handling", "[IntervalPropertyComputer][Error]") {
3✔
530

531
    SECTION("ExecutionPlan without intervals throws exception") {
3✔
532
        // Create time frame
533
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
534
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
535

536
        // Create source intervals
537
        std::vector<Interval> sourceIntervals = {{1, 3}};
3✔
538
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
539
                "TestIntervals", timeFrame, sourceIntervals);
1✔
540

541
        // Create execution plan with indices instead of intervals
542
        std::vector<TimeFrameIndex> indices = {TimeFrameIndex(0), TimeFrameIndex(1)};
3✔
543
        ExecutionPlan plan(indices, timeFrame);
1✔
544

545
        // Create the computer
546
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
547
                                                   IntervalProperty::Start,
548
                                                   "TestIntervals");
3✔
549

550
        // Should throw an exception
551
        REQUIRE_THROWS_AS(computer.compute(plan), std::runtime_error);
2✔
552
    }
4✔
553

554
    SECTION("Row intervals not matching source intervals throws exception") {
3✔
555
        // Create time frame
556
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
557
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
558

559
        // Create source intervals
560
        std::vector<Interval> sourceIntervals = {
1✔
561
            {1, 3},
562
            {5, 7}
563
        };
3✔
564
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
565
                "TestIntervals", timeFrame, sourceIntervals);
1✔
566

567
        // Create row intervals that don't match source intervals
568
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
569
                TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(4)), // Not in source
570
                TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(8))  // Not in source
571
        };
3✔
572

573
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
574

575
        // Create the computer
576
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
577
                                                   IntervalProperty::Start,
578
                                                   "TestIntervals");
3✔
579

580
        // Should throw an exception
581
        REQUIRE_THROWS_AS(computer.compute(plan), std::runtime_error);
2✔
582
    }
4✔
583

584
    SECTION("Partial subset of source intervals is valid") {
3✔
585
        // Create time frame
586
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
587
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
588

589
        // Create source intervals (5 intervals)
590
        std::vector<Interval> sourceIntervals = {
1✔
591
            {1, 2},  // Interval 1
592
            {3, 4},  // Interval 2
593
            {5, 6},  // Interval 3
594
            {7, 8},  // Interval 4
595
            {9, 10}  // Interval 5
596
        };
3✔
597
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
598
                "TestIntervals", timeFrame, sourceIntervals);
1✔
599

600
        // Create row intervals that are a subset of source intervals (intervals 2 and 4)
601
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
602
                TimeFrameInterval(TimeFrameIndex(3), TimeFrameIndex(4)), // Matches source interval 2
603
                TimeFrameInterval(TimeFrameIndex(7), TimeFrameIndex(8))  // Matches source interval 4
604
        };
3✔
605

606
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
607

608
        // Create the computer
609
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
610
                                                   IntervalProperty::Start,
611
                                                   "TestIntervals");
3✔
612

613
        // Should work without throwing
614
        auto [results, entity_ids] = computer.compute(plan);
1✔
615

616
        // Verify results
617
        REQUIRE(results.size() == 2);
1✔
618
        REQUIRE(results[0] == 3); // Start of interval 2
1✔
619
        REQUIRE(results[1] == 7); // Start of interval 4
1✔
620
    }
4✔
621
}
3✔
622

623
TEST_CASE("DM - TV - IntervalPropertyComputer Template Types", "[IntervalPropertyComputer][Templates]") {
1✔
624

625
    SECTION("Test with different numeric types") {
1✔
626
        // Create time frame
627
        std::vector<int> timeValues = {0, 5, 10, 15, 20, 25};
3✔
628
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
629

630
        // Create source intervals
631
        std::vector<Interval> sourceIntervals = {{1, 4}}; // Duration = 3
3✔
632
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
633
                "TestIntervals", timeFrame, sourceIntervals);
1✔
634

635
        // Create row intervals that match the source intervals exactly
636
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
637
                TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(4))// Start=1, End=4, Duration=3
638
        };
3✔
639

640
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
641

642
        // Test with int64_t
643
        IntervalPropertyComputer<int64_t> intComputer(intervalSource,
1✔
644
                                                      IntervalProperty::Duration,
645
                                                      "TestIntervals");
3✔
646
        auto [intResults, entity_ids] = intComputer.compute(plan);
1✔
647
        REQUIRE(intResults.size() == 1);
1✔
648
        REQUIRE(intResults[0] == 3);
1✔
649

650
        // Test with float
651
        IntervalPropertyComputer<float> floatComputer(intervalSource,
1✔
652
                                                      IntervalProperty::Duration,
653
                                                      "TestIntervals");
3✔
654
        auto [floatResults, floatEntity_ids] = floatComputer.compute(plan);
1✔
655
        REQUIRE(floatResults.size() == 1);
1✔
656
        REQUIRE(floatResults[0] == Catch::Approx(3.0f));
1✔
657

658
        // Test with double
659
        IntervalPropertyComputer<double> doubleComputer(intervalSource,
1✔
660
                                                        IntervalProperty::Duration,
661
                                                        "TestIntervals");
3✔
662
        auto [doubleResults, doubleEntity_ids] = doubleComputer.compute(plan);
1✔
663
        REQUIRE(doubleResults.size() == 1);
1✔
664
        REQUIRE(doubleResults[0] == Catch::Approx(3.0));
1✔
665
    }
2✔
666
}
1✔
667

668
TEST_CASE("DM - TV - IntervalPropertyComputer Dependency Tracking", "[IntervalPropertyComputer][Dependencies]") {
1✔
669

670
    SECTION("getSourceDependency returns correct source name") {
1✔
671
        // Create minimal setup
672
        std::vector<int> timeValues = {0, 1, 2};
3✔
673
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
674

675
        std::vector<Interval> sourceIntervals = {{0, 1}};
3✔
676
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
677
                "TestSource", timeFrame, sourceIntervals);
1✔
678

679
        // Create computer
680
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
681
                                                   IntervalProperty::Start,
682
                                                   "TestSourceName");
3✔
683

684
        // Test source dependency
685
        REQUIRE(computer.getSourceDependency() == "TestSourceName");
1✔
686
    }
2✔
687
}
1✔
688

689
TEST_CASE_METHOD(IntervalPropertyTestFixture, "DM - TV - IntervalPropertyComputer with DataManager fixture", "[IntervalPropertyComputer][DataManager][Fixture]") {
2✔
690

691
    SECTION("Test with behavior intervals from fixture") {
2✔
692
        auto & dm = getDataManager();
1✔
693
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
694

695
        // Get the interval source from the DataManager
696
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
697

698
        REQUIRE(behavior_source != nullptr);
1✔
699

700
        // Create row selector from behavior intervals
701
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
702
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
703
                TimeFrameIndex(0), TimeFrameIndex(200), behavior_time_frame.get());
1✔
704

705
        REQUIRE(behavior_intervals.size() == 5);// 5 behavior periods from fixture
1✔
706

707
        // Convert to TimeFrameIntervals for row selector
708
        std::vector<TimeFrameInterval> row_intervals;
1✔
709
        for (auto const & interval: behavior_intervals) {
6✔
710
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
5✔
711
        }
712

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

715
        // Create TableView builder
716
        TableViewBuilder builder(dme);
1✔
717
        builder.setRowSelector(std::move(row_selector));
1✔
718

719
        // Test Start property
720
        builder.addColumn<double>("IntervalStart",
5✔
721
                                  std::make_unique<IntervalPropertyComputer<double>>(behavior_source,
3✔
722
                                                                                     IntervalProperty::Start, "BehaviorPeriods"));
2✔
723

724
        // Test End property
725
        builder.addColumn<double>("IntervalEnd",
5✔
726
                                  std::make_unique<IntervalPropertyComputer<double>>(behavior_source,
3✔
727
                                                                                     IntervalProperty::End, "BehaviorPeriods"));
2✔
728

729
        // Test Duration property
730
        builder.addColumn<double>("IntervalDuration",
5✔
731
                                  std::make_unique<IntervalPropertyComputer<double>>(behavior_source,
3✔
732
                                                                                     IntervalProperty::Duration, "BehaviorPeriods"));
2✔
733

734
        // Build the table
735
        TableView table = builder.build();
1✔
736

737
        // Verify table structure
738
        REQUIRE(table.getRowCount() == 5);
1✔
739
        REQUIRE(table.getColumnCount() == 3);
1✔
740
        REQUIRE(table.hasColumn("IntervalStart"));
3✔
741
        REQUIRE(table.hasColumn("IntervalEnd"));
3✔
742
        REQUIRE(table.hasColumn("IntervalDuration"));
3✔
743

744
        // Get the column data
745
        auto starts = table.getColumnValues<double>("IntervalStart");
3✔
746
        auto ends = table.getColumnValues<double>("IntervalEnd");
3✔
747
        auto durations = table.getColumnValues<double>("IntervalDuration");
3✔
748

749
        REQUIRE(starts.size() == 5);
1✔
750
        REQUIRE(ends.size() == 5);
1✔
751
        REQUIRE(durations.size() == 5);
1✔
752

753
        // Verify the property values match our test data expectations
754
        // Expected behavior periods from fixture:
755
        // Period 1: 10-15 (duration = 5)
756
        // Period 2: 30-50 (duration = 20)
757
        // Period 3: 70-120 (duration = 50)
758
        // Period 4: 150-155 (duration = 5)
759
        // Period 5: 160-190 (duration = 30)
760

761
        for (size_t i = 0; i < 5; ++i) {
6✔
762
            REQUIRE(starts[i] >= 0);                       // Valid start times
5✔
763
            REQUIRE(ends[i] > starts[i]);                  // End > Start
5✔
764
            REQUIRE(durations[i] > 0);                     // Positive durations
5✔
765
            REQUIRE(durations[i] == (ends[i] - starts[i]));// Duration = End - Start
5✔
766

767
            std::cout << "Behavior period " << i << ": Start=" << starts[i]
5✔
768
                      << ", End=" << ends[i] << ", Duration=" << durations[i] << std::endl;
5✔
769
        }
770

771
        // Verify specific expected values (first few intervals)
772
        REQUIRE(starts[0] == Catch::Approx(10.0));  // First period starts at 10
1✔
773
        REQUIRE(ends[0] == Catch::Approx(15.0));    // First period ends at 15
1✔
774
        REQUIRE(durations[0] == Catch::Approx(5.0));// First period duration = 5
1✔
775

776
        REQUIRE(starts[1] == Catch::Approx(30.0));   // Second period starts at 30
1✔
777
        REQUIRE(durations[1] == Catch::Approx(20.0));// Second period duration = 20
1✔
778
    }
3✔
779

780
    SECTION("Test with neural events from fixture") {
2✔
781
        auto & dm = getDataManager();
1✔
782
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
783

784
        // Get neural events from high-resolution timeframe
785
        auto neural_source = dme->getIntervalSource("NeuralEvents");
3✔
786
        REQUIRE(neural_source != nullptr);
1✔
787

788
        auto neural_time_frame = dm.getTime(TimeKey("high_res_time"));
1✔
789
        auto neural_intervals = neural_source->getIntervalsInRange(
1✔
790
                TimeFrameIndex(0), TimeFrameIndex(200), neural_time_frame.get());
1✔
791

792
        REQUIRE(neural_intervals.size() == 4);// 4 neural events from fixture
1✔
793

794
        // Convert to TimeFrameIntervals for row selector
795
        std::vector<TimeFrameInterval> row_intervals;
1✔
796
        for (auto const & interval: neural_intervals) {
5✔
797
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
798
        }
799

800
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, neural_time_frame);
1✔
801

802
        TableViewBuilder builder(dme);
1✔
803
        builder.setRowSelector(std::move(row_selector));
1✔
804

805
        // Add property computers
806
        builder.addColumn<int64_t>("EventStart",
5✔
807
                                   std::make_unique<IntervalPropertyComputer<int64_t>>(neural_source,
3✔
808
                                                                                       IntervalProperty::Start, "NeuralEvents"));
2✔
809

810
        builder.addColumn<int64_t>("EventDuration",
5✔
811
                                   std::make_unique<IntervalPropertyComputer<int64_t>>(neural_source,
3✔
812
                                                                                       IntervalProperty::Duration, "NeuralEvents"));
2✔
813

814
        // Build and verify the table
815
        TableView table = builder.build();
1✔
816

817
        REQUIRE(table.getRowCount() == 4);
1✔
818
        REQUIRE(table.getColumnCount() == 2);
1✔
819

820
        auto starts = table.getColumnValues<int64_t>("EventStart");
3✔
821
        auto durations = table.getColumnValues<int64_t>("EventDuration");
3✔
822

823
        REQUIRE(starts.size() == 4);
1✔
824
        REQUIRE(durations.size() == 4);
1✔
825

826
        // Verify neural events match our test data:
827
        // Event 1: 12-13 (duration = 1)
828
        // Event 2: 45-47 (duration = 2)
829
        // Event 3: 89-92 (duration = 3)
830
        // Event 4: 156-161 (duration = 5)
831

832
        std::vector<int64_t> expected_starts = {12, 45, 89, 156};
3✔
833
        std::vector<int64_t> expected_durations = {1, 2, 3, 5};
3✔
834

835
        for (size_t i = 0; i < 4; ++i) {
5✔
836
            REQUIRE(starts[i] == expected_starts[i]);
4✔
837
            REQUIRE(durations[i] == expected_durations[i]);
4✔
838

839
            std::cout << "Neural event " << i << ": Start=" << starts[i]
4✔
840
                      << ", Duration=" << durations[i] << std::endl;
4✔
841
        }
842
    }
3✔
843
}
2✔
844

845
TEST_CASE_METHOD(IntervalPropertyTestFixture, "DM - TV - IntervalPropertyComputer EntityID Round-trip Integration", "[IntervalPropertyComputer][EntityID][integration]") {
1✔
846

847
    SECTION("TableView creation and EntityID extraction with IntervalPropertyComputer") {
1✔
848
        auto & dm = getDataManager();
1✔
849

850
        // Create DataManagerExtension for TableView integration
851
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
852

853
        // Get behavior intervals from our test fixture
854
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
855
        REQUIRE(behavior_source != nullptr);
1✔
856

857
        // Create row selector for behavior intervals
858
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
859
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
860
                TimeFrameIndex(0), TimeFrameIndex(200), behavior_time_frame.get());
1✔
861

862
        REQUIRE(behavior_intervals.size() == 5);// 5 behavior periods from fixture
1✔
863

864
        // Convert to TimeFrameIntervals for row selector
865
        std::vector<TimeFrameInterval> row_intervals;
1✔
866
        for (auto const & interval: behavior_intervals) {
6✔
867
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
5✔
868
        }
869

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

872
        // Build TableView using TableViewBuilder
873
        TableViewBuilder builder(dme);
1✔
874
        builder.setRowSelector(std::move(row_selector));
1✔
875

876
        // Add IntervalPropertyComputer columns
877
        builder.addColumn<double>("IntervalStart",
5✔
878
                                  std::make_unique<IntervalPropertyComputer<double>>(behavior_source,
3✔
879
                                                                                     IntervalProperty::Start, "BehaviorPeriods"));
2✔
880
        builder.addColumn<double>("IntervalDuration",
5✔
881
                                  std::make_unique<IntervalPropertyComputer<double>>(behavior_source,
3✔
882
                                                                                     IntervalProperty::Duration, "BehaviorPeriods"));
2✔
883

884
        auto table = builder.build();
1✔
885

886
        auto start_data_from_table = table.getColumnValues<double>("IntervalStart");
3✔
887
        auto duration_data_from_table = table.getColumnValues<double>("IntervalDuration");
3✔
888

889
        // Verify table structure
890
        REQUIRE(table.getRowCount() == 5);// 5 behavior periods
1✔
891
        REQUIRE(table.getColumnCount() == 2);
1✔
892
        REQUIRE(table.hasColumn("IntervalStart"));
3✔
893
        REQUIRE(table.hasColumn("IntervalDuration"));
3✔
894

895
        // Verify EntityID information is available for IntervalPropertyComputer columns
896
        REQUIRE(table.hasColumnEntityIds("IntervalStart"));
3✔
897
        REQUIRE(table.hasColumnEntityIds("IntervalDuration"));
3✔
898

899
        // Get EntityIDs from the columns (all IntervalPropertyComputer columns should share the same EntityIDs)
900
        auto start_entity_ids_variant = table.getColumnEntityIds("IntervalStart");
3✔
901
        auto duration_entity_ids_variant = table.getColumnEntityIds("IntervalDuration");
3✔
902

903
        // IntervalPropertyComputer uses Simple EntityID structure, so extract std::vector<EntityId>
904
        REQUIRE(std::holds_alternative<std::vector<EntityId>>(start_entity_ids_variant));
1✔
905
        REQUIRE(std::holds_alternative<std::vector<EntityId>>(duration_entity_ids_variant));
1✔
906

907
        auto start_entity_ids = std::get<std::vector<EntityId>>(start_entity_ids_variant);
1✔
908
        auto duration_entity_ids = std::get<std::vector<EntityId>>(duration_entity_ids_variant);
1✔
909

910
        REQUIRE(start_entity_ids.size() == 5);// Should match row count
1✔
911
        REQUIRE(duration_entity_ids.size() == 5);
1✔
912

913
        // Verify all EntityIDs are valid (non-zero)
914
        for (EntityId id: start_entity_ids) {
6✔
915
            REQUIRE(id != 0);
5✔
916
            INFO("Start Column EntityID: " << id);
5✔
917
        }
5✔
918

919
        // Verify that both columns have the same EntityIDs (they come from the same intervals)
920
        REQUIRE(duration_entity_ids == start_entity_ids);
1✔
921

922
        // Get sample data from table columns
923
        auto start_values = table.getColumnValues<double>("IntervalStart");
3✔
924
        auto duration_values = table.getColumnValues<double>("IntervalDuration");
3✔
925

926
        REQUIRE(start_values.size() == 5);
1✔
927
        REQUIRE(duration_values.size() == 5);
1✔
928

929
        // Verify data consistency and entity relationships
930
        for (size_t i = 0; i < 5; ++i) {
6✔
931
            // Verify basic properties
932
            REQUIRE(start_values[i] >= 0);
5✔
933
            REQUIRE(duration_values[i] > 0);
5✔
934

935
            INFO("Row " << i << ": Start=" << start_values[i]
5✔
936
                        << ", Duration=" << duration_values[i]
937
                        << ", EntityID=" << start_entity_ids[i]);
938
        }
5✔
939

940
        // Test EntityID round-trip: Compare TableView EntityIDs with original source EntityIDs
941
        INFO("Testing EntityID round-trip from source data to TableView");
1✔
942

943
        // Get the original DigitalIntervalSeries data that was used to create the intervals
944
        auto behavior_data = dm.getData<DigitalIntervalSeries>("BehaviorPeriods");
3✔
945
        REQUIRE(behavior_data != nullptr);
1✔
946

947
        // Get the EntityIDs directly from the source data
948
        auto source_entity_ids = behavior_data->getEntityIds();
1✔
949
        REQUIRE(source_entity_ids.size() == 5);// Should match the number of intervals
1✔
950

951
        // Debug: Print source EntityIDs to see if they're valid
952
        INFO("Source EntityIDs from DigitalIntervalSeries:");
1✔
953
        for (size_t i = 0; i < source_entity_ids.size(); ++i) {
6✔
954
            INFO("  Source EntityID[" << i << "] = " << source_entity_ids[i]);
5✔
955
            REQUIRE(source_entity_ids[i] != 0);// Verify source EntityIDs are valid
5✔
956
        }
5✔
957

958
        // Verify that TableView EntityIDs match the source EntityIDs
959
        REQUIRE(start_entity_ids.size() == source_entity_ids.size());
1✔
960
        for (size_t i = 0; i < source_entity_ids.size(); ++i) {
6✔
961
            REQUIRE(start_entity_ids[i] == source_entity_ids[i]);
5✔
962
            INFO("EntityID " << i << ": Source=" << source_entity_ids[i]
5✔
963
                             << ", TableView=" << start_entity_ids[i] << " ✓");
964
        }
5✔
965

966
        INFO("✓ EntityID round-trip successful: " << start_entity_ids.size() << " EntityIDs match source data");
1✔
967

968
        // Test individual row EntityID extraction
969
        for (size_t row_idx = 0; row_idx < 5; ++row_idx) {
6✔
970
            // Get EntityIDs for a specific row
971
            auto row_start_ids = table.getCellEntityIds("IntervalStart", row_idx);
15✔
972
            auto row_duration_ids = table.getCellEntityIds("IntervalDuration", row_idx);
15✔
973

974
            REQUIRE(row_start_ids.size() == 1);// Should have exactly one EntityID per interval
5✔
975
            REQUIRE(row_duration_ids.size() == 1);
5✔
976

977
            // Verify they match the overall EntityID list
978
            REQUIRE(row_start_ids[0] == start_entity_ids[row_idx]);
5✔
979
            REQUIRE(row_duration_ids[0] == start_entity_ids[row_idx]);
5✔
980

981
            INFO("Row " << row_idx << " individual EntityID check: " << row_start_ids[0]);
5✔
982
        }
5✔
983

984
        INFO("✓ All IntervalPropertyComputer EntityID extraction tests passed");
1✔
985
    }
2✔
986
}
1✔
987

988
TEST_CASE_METHOD(IntervalPropertyTableRegistryTestFixture, "DM - TV - IntervalPropertyComputer via ComputerRegistry", "[IntervalPropertyComputer][Registry]") {
3✔
989

990
    SECTION("Verify IntervalPropertyComputer is registered in ComputerRegistry") {
3✔
991
        auto & registry = getTableRegistry().getComputerRegistry();
1✔
992

993
        // Check that all interval property computers are registered
994
        auto start_info = registry.findComputerInfo("Interval Start");
3✔
995
        auto end_info = registry.findComputerInfo("Interval End");
3✔
996
        auto duration_info = registry.findComputerInfo("Interval Duration");
3✔
997

998
        REQUIRE(start_info != nullptr);
1✔
999
        REQUIRE(end_info != nullptr);
1✔
1000
        REQUIRE(duration_info != nullptr);
1✔
1001

1002
        // Verify computer info details for Start
1003
        REQUIRE(start_info->name == "Interval Start");
1✔
1004
        REQUIRE(start_info->outputType == typeid(double));
1✔
1005
        REQUIRE(start_info->outputTypeName == "double");
1✔
1006
        REQUIRE(start_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
1007
        REQUIRE(start_info->requiredSourceType == typeid(std::shared_ptr<IIntervalSource>));
1✔
1008

1009
        // Verify computer info details for End
1010
        REQUIRE(end_info->name == "Interval End");
1✔
1011
        REQUIRE(end_info->outputType == typeid(double));
1✔
1012
        REQUIRE(end_info->outputTypeName == "double");
1✔
1013
        REQUIRE(end_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
1014
        REQUIRE(end_info->requiredSourceType == typeid(std::shared_ptr<IIntervalSource>));
1✔
1015

1016
        // Verify computer info details for Duration
1017
        REQUIRE(duration_info->name == "Interval Duration");
1✔
1018
        REQUIRE(duration_info->outputType == typeid(double));
1✔
1019
        REQUIRE(duration_info->outputTypeName == "double");
1✔
1020
        REQUIRE(duration_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
1021
        REQUIRE(duration_info->requiredSourceType == typeid(std::shared_ptr<IIntervalSource>));
1✔
1022
    }
3✔
1023

1024
    SECTION("Create IntervalPropertyComputer via ComputerRegistry") {
3✔
1025
        auto & dm = getDataManager();
1✔
1026
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1027
        auto & registry = getTableRegistry().getComputerRegistry();
1✔
1028

1029
        // Get behavior source for testing
1030
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1031
        REQUIRE(behavior_source != nullptr);
1✔
1032

1033
        // Create computers via registry
1034
        std::map<std::string, std::string> empty_params;
1✔
1035

1036
        auto start_computer = registry.createTypedComputer<double>(
1✔
1037
                "Interval Start", behavior_source, empty_params);
3✔
1038
        auto end_computer = registry.createTypedComputer<double>(
1✔
1039
                "Interval End", behavior_source, empty_params);
3✔
1040
        auto duration_computer = registry.createTypedComputer<double>(
1✔
1041
                "Interval Duration", behavior_source, empty_params);
3✔
1042

1043
        REQUIRE(start_computer != nullptr);
1✔
1044
        REQUIRE(end_computer != nullptr);
1✔
1045
        REQUIRE(duration_computer != nullptr);
1✔
1046

1047
        // Test that the created computers work correctly
1048
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1049

1050
        // Create a simple test interval
1051
        std::vector<TimeFrameInterval> test_intervals = {
1✔
1052
                TimeFrameInterval(TimeFrameIndex(30), TimeFrameIndex(50))// Behavior period 2
1053
        };
3✔
1054

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

1057
        TableViewBuilder builder(dme);
1✔
1058
        builder.setRowSelector(std::move(row_selector));
1✔
1059

1060
        // Use the registry-created computers
1061
        builder.addColumn("RegistryStart", std::move(start_computer));
3✔
1062
        builder.addColumn("RegistryEnd", std::move(end_computer));
3✔
1063
        builder.addColumn("RegistryDuration", std::move(duration_computer));
3✔
1064

1065
        // Build and verify the table
1066
        TableView table = builder.build();
1✔
1067

1068
        REQUIRE(table.getRowCount() == 1);
1✔
1069
        REQUIRE(table.getColumnCount() == 3);
1✔
1070
        REQUIRE(table.hasColumn("RegistryStart"));
3✔
1071
        REQUIRE(table.hasColumn("RegistryEnd"));
3✔
1072
        REQUIRE(table.hasColumn("RegistryDuration"));
3✔
1073

1074
        auto starts = table.getColumnValues<double>("RegistryStart");
3✔
1075
        auto ends = table.getColumnValues<double>("RegistryEnd");
3✔
1076
        auto durations = table.getColumnValues<double>("RegistryDuration");
3✔
1077

1078
        REQUIRE(starts.size() == 1);
1✔
1079
        REQUIRE(ends.size() == 1);
1✔
1080
        REQUIRE(durations.size() == 1);
1✔
1081

1082
        // Verify reasonable results (should match the test interval: 30-50)
1083
        REQUIRE(starts[0] == Catch::Approx(30.0));
1✔
1084
        REQUIRE(ends[0] == Catch::Approx(50.0));
1✔
1085
        REQUIRE(durations[0] == Catch::Approx(20.0));
1✔
1086

1087
        std::cout << "Registry test - Start: " << starts[0]
1✔
1088
                  << ", End: " << ends[0] << ", Duration: " << durations[0] << std::endl;
1✔
1089
    }
4✔
1090

1091
    SECTION("Compare registry-created vs direct-created computers") {
3✔
1092
        auto & dm = getDataManager();
1✔
1093
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
1094
        auto & registry = getTableRegistry().getComputerRegistry();
1✔
1095

1096
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
1097
        REQUIRE(behavior_source != nullptr);
1✔
1098

1099
        // Create computer via registry
1100
        std::map<std::string, std::string> empty_params;
1✔
1101
        auto registry_computer = registry.createTypedComputer<double>(
1✔
1102
                "Interval Duration", behavior_source, empty_params);
3✔
1103

1104
        // Create computer directly
1105
        auto direct_computer = std::make_unique<IntervalPropertyComputer<double>>(
1✔
1106
                behavior_source, IntervalProperty::Duration, "BehaviorPeriods");
1✔
1107

1108
        REQUIRE(registry_computer != nullptr);
1✔
1109
        REQUIRE(direct_computer != nullptr);
1✔
1110

1111
        // Test both computers with the same data
1112
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1113
        std::vector<TimeFrameInterval> test_intervals = {
1✔
1114
                TimeFrameInterval(TimeFrameIndex(70), TimeFrameIndex(120))// Period 3: duration = 50
1115
        };
3✔
1116

1117
        ExecutionPlan plan(test_intervals, behavior_time_frame);
1✔
1118

1119
        auto [registry_result, registry_entity_ids] = registry_computer->compute(plan);
1✔
1120
        auto [direct_result, direct_entity_ids] = direct_computer->compute(plan);
1✔
1121

1122
        REQUIRE(registry_result.size() == 1);
1✔
1123
        REQUIRE(direct_result.size() == 1);
1✔
1124

1125
        // Results should be identical
1126
        REQUIRE(registry_result[0] == Catch::Approx(direct_result[0]));
1✔
1127
        REQUIRE(registry_result[0] == Catch::Approx(50.0));// Expected duration
1✔
1128

1129
        std::cout << "Comparison test - Registry result: " << registry_result[0]
1✔
1130
                  << ", Direct result: " << direct_result[0] << std::endl;
1✔
1131
    }
4✔
1132
}
3✔
1133

1134
TEST_CASE_METHOD(IntervalPropertyTableRegistryTestFixture, "DM - TV - IntervalPropertyComputer via JSON TablePipeline", "[IntervalPropertyComputer][JSON][Pipeline]") {
2✔
1135

1136
    SECTION("Test all property operations via JSON pipeline") {
2✔
1137
        // JSON configuration for testing IntervalPropertyComputer through TablePipeline
1138
        char const * json_config = R"({
1✔
1139
            "metadata": {
1140
                "name": "Interval Property Test",
1141
                "description": "Test JSON execution of IntervalPropertyComputer",
1142
                "version": "1.0"
1143
            },
1144
            "tables": [
1145
                {
1146
                    "table_id": "interval_property_test",
1147
                    "name": "Interval Property Test Table",
1148
                    "description": "Test table using IntervalPropertyComputer",
1149
                    "row_selector": {
1150
                        "type": "interval",
1151
                        "source": "BehaviorPeriods"
1152
                    },
1153
                    "columns": [
1154
                        {
1155
                            "name": "IntervalStartTime",
1156
                            "description": "Start time of each behavior period",
1157
                            "data_source": "BehaviorPeriods",
1158
                            "computer": "Interval Start"
1159
                        },
1160
                        {
1161
                            "name": "IntervalEndTime",
1162
                            "description": "End time of each behavior period",
1163
                            "data_source": "BehaviorPeriods",
1164
                            "computer": "Interval End"
1165
                        },
1166
                        {
1167
                            "name": "IntervalDuration",
1168
                            "description": "Duration of each behavior period",
1169
                            "data_source": "BehaviorPeriods",
1170
                            "computer": "Interval Duration"
1171
                        }
1172
                    ]
1173
                }
1174
            ]
1175
        })";
1176

1177
        auto & pipeline = getTablePipeline();
1✔
1178

1179
        // Parse the JSON configuration
1180
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1181

1182
        // Load configuration into pipeline
1183
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1184
        REQUIRE(load_success);
1✔
1185

1186
        // Verify configuration was loaded correctly
1187
        auto table_configs = pipeline.getTableConfigurations();
1✔
1188
        REQUIRE(table_configs.size() == 1);
1✔
1189

1190
        auto const & config = table_configs[0];
1✔
1191
        REQUIRE(config.table_id == "interval_property_test");
1✔
1192
        REQUIRE(config.name == "Interval Property Test Table");
1✔
1193
        REQUIRE(config.columns.size() == 3);
1✔
1194

1195
        // Verify column configurations
1196
        auto const & column1 = config.columns[0];
1✔
1197
        REQUIRE(column1["name"] == "IntervalStartTime");
1✔
1198
        REQUIRE(column1["computer"] == "Interval Start");
1✔
1199
        REQUIRE(column1["data_source"] == "BehaviorPeriods");
1✔
1200

1201
        auto const & column2 = config.columns[1];
1✔
1202
        REQUIRE(column2["name"] == "IntervalEndTime");
1✔
1203
        REQUIRE(column2["computer"] == "Interval End");
1✔
1204
        REQUIRE(column2["data_source"] == "BehaviorPeriods");
1✔
1205

1206
        auto const & column3 = config.columns[2];
1✔
1207
        REQUIRE(column3["name"] == "IntervalDuration");
1✔
1208
        REQUIRE(column3["computer"] == "Interval Duration");
1✔
1209
        REQUIRE(column3["data_source"] == "BehaviorPeriods");
1✔
1210

1211
        // Verify row selector configuration
1212
        REQUIRE(config.row_selector["type"] == "interval");
1✔
1213
        REQUIRE(config.row_selector["source"] == "BehaviorPeriods");
1✔
1214

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

1217
        // Execute the pipeline
1218
        auto pipeline_result = pipeline.execute([](int table_index, std::string const & table_name, int table_progress, int overall_progress) {
2✔
1219
            std::cout << "Building table " << table_index << " (" << table_name << "): "
5✔
1220
                      << table_progress << "% (Overall: " << overall_progress << "%)" << std::endl;
5✔
1221
        });
2✔
1222

1223
        if (pipeline_result.success) {
1✔
1224
            std::cout << "Pipeline executed successfully!" << std::endl;
1✔
1225
            std::cout << "Tables completed: " << pipeline_result.tables_completed << "/" << pipeline_result.total_tables << std::endl;
1✔
1226
            std::cout << "Execution time: " << pipeline_result.total_execution_time_ms << " ms" << std::endl;
1✔
1227

1228
            // Verify the built table exists
1229
            auto & registry = getTableRegistry();
1✔
1230
            REQUIRE(registry.hasTable("interval_property_test"));
3✔
1231

1232
            // Get the built table and verify its structure
1233
            auto built_table = registry.getBuiltTable("interval_property_test");
3✔
1234
            REQUIRE(built_table != nullptr);
1✔
1235

1236
            // Check that the table has the expected columns
1237
            auto column_names = built_table->getColumnNames();
1✔
1238
            std::cout << "Built table has " << column_names.size() << " columns" << std::endl;
1✔
1239
            for (auto const & name: column_names) {
4✔
1240
                std::cout << "  Column: " << name << std::endl;
3✔
1241
            }
1242

1243
            REQUIRE(column_names.size() == 3);
1✔
1244
            REQUIRE(built_table->hasColumn("IntervalStartTime"));
3✔
1245
            REQUIRE(built_table->hasColumn("IntervalEndTime"));
3✔
1246
            REQUIRE(built_table->hasColumn("IntervalDuration"));
3✔
1247

1248
            // Verify table has 5 rows (one for each behavior period)
1249
            REQUIRE(built_table->getRowCount() == 5);
1✔
1250

1251
            // Get and verify the computed values
1252
            auto start_times = built_table->getColumnValues<double>("IntervalStartTime");
3✔
1253
            auto end_times = built_table->getColumnValues<double>("IntervalEndTime");
3✔
1254
            auto durations = built_table->getColumnValues<double>("IntervalDuration");
3✔
1255

1256
            REQUIRE(start_times.size() == 5);
1✔
1257
            REQUIRE(end_times.size() == 5);
1✔
1258
            REQUIRE(durations.size() == 5);
1✔
1259

1260
            for (size_t i = 0; i < 5; ++i) {
6✔
1261
                REQUIRE(start_times[i] >= 0);                                         // Valid start times
5✔
1262
                REQUIRE(end_times[i] > start_times[i]);                               // End > Start
5✔
1263
                REQUIRE(durations[i] > 0);                                            // Positive durations
5✔
1264
                REQUIRE(durations[i] == Catch::Approx(end_times[i] - start_times[i]));// Duration = End - Start
5✔
1265

1266
                std::cout << "Row " << i << ": Start=" << start_times[i]
5✔
1267
                          << ", End=" << end_times[i] << ", Duration=" << durations[i] << std::endl;
5✔
1268
            }
1269

1270
            // Verify specific expected values from our fixture
1271
            REQUIRE(start_times[0] == Catch::Approx(10.0));// First period: 10-15
1✔
1272
            REQUIRE(end_times[0] == Catch::Approx(15.0));
1✔
1273
            REQUIRE(durations[0] == Catch::Approx(5.0));
1✔
1274

1275
            REQUIRE(start_times[1] == Catch::Approx(30.0));// Second period: 30-50
1✔
1276
            REQUIRE(durations[1] == Catch::Approx(20.0));
1✔
1277

1278
        } else {
1✔
UNCOV
1279
            std::cout << "Pipeline execution failed: " << pipeline_result.error_message << std::endl;
×
UNCOV
1280
            FAIL("Pipeline execution failed: " + pipeline_result.error_message);
×
1281
        }
1282
    }
3✔
1283

1284
    SECTION("Test mixed interval properties with neural data") {
2✔
1285
        char const * json_config = R"({
1✔
1286
            "metadata": {
1287
                "name": "Neural Event Property Test",
1288
                "description": "Test IntervalPropertyComputer with neural events"
1289
            },
1290
            "tables": [
1291
                {
1292
                    "table_id": "neural_property_test",
1293
                    "name": "Neural Event Property Table",
1294
                    "description": "Test table using neural event intervals",
1295
                    "row_selector": {
1296
                        "type": "interval",
1297
                        "source": "NeuralEvents"
1298
                    },
1299
                    "columns": [
1300
                        {
1301
                            "name": "EventStart",
1302
                            "description": "Start time of neural events",
1303
                            "data_source": "NeuralEvents",
1304
                            "computer": "Interval Start"
1305
                        },
1306
                        {
1307
                            "name": "EventDuration",
1308
                            "description": "Duration of neural events",
1309
                            "data_source": "NeuralEvents",
1310
                            "computer": "Interval Duration"
1311
                        }
1312
                    ]
1313
                }
1314
            ]
1315
        })";
1316

1317
        auto & pipeline = getTablePipeline();
1✔
1318
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1319

1320
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1321
        REQUIRE(load_success);
1✔
1322

1323
        auto table_configs = pipeline.getTableConfigurations();
1✔
1324
        REQUIRE(table_configs.size() == 1);
1✔
1325

1326
        auto const & config = table_configs[0];
1✔
1327
        REQUIRE(config.columns.size() == 2);
1✔
1328
        REQUIRE(config.columns[0]["computer"] == "Interval Start");
1✔
1329
        REQUIRE(config.columns[1]["computer"] == "Interval Duration");
1✔
1330

1331
        std::cout << "Neural event properties JSON configuration parsed successfully" << std::endl;
1✔
1332

1333
        // Execute the pipeline
1334
        auto pipeline_result = pipeline.execute();
1✔
1335

1336
        if (pipeline_result.success) {
1✔
1337
            std::cout << "✓ Neural event property pipeline executed successfully!" << std::endl;
1✔
1338

1339
            // Verify the built table exists and has correct structure
1340
            auto & registry = getTableRegistry();
1✔
1341
            REQUIRE(registry.hasTable("neural_property_test"));
3✔
1342

1343
            auto built_table = registry.getBuiltTable("neural_property_test");
3✔
1344
            REQUIRE(built_table != nullptr);
1✔
1345

1346
            // Should have 4 rows (one for each neural event from our fixture)
1347
            REQUIRE(built_table->getRowCount() == 4);
1✔
1348
            REQUIRE(built_table->getColumnCount() == 2);
1✔
1349
            REQUIRE(built_table->hasColumn("EventStart"));
3✔
1350
            REQUIRE(built_table->hasColumn("EventDuration"));
3✔
1351

1352
            auto starts = built_table->getColumnValues<double>("EventStart");
3✔
1353
            auto durations = built_table->getColumnValues<double>("EventDuration");
3✔
1354

1355
            REQUIRE(starts.size() == 4);
1✔
1356
            REQUIRE(durations.size() == 4);
1✔
1357

1358
            // Expected neural events from fixture:
1359
            // Event 1: 12-13 (duration = 1)
1360
            // Event 2: 45-47 (duration = 2)
1361
            // Event 3: 89-92 (duration = 3)
1362
            // Event 4: 156-161 (duration = 5)
1363
            std::vector<double> expected_starts = {12.0, 45.0, 89.0, 156.0};
3✔
1364
            std::vector<double> expected_durations = {1.0, 2.0, 3.0, 5.0};
3✔
1365

1366
            for (size_t i = 0; i < 4; ++i) {
5✔
1367
                REQUIRE(starts[i] == Catch::Approx(expected_starts[i]));
4✔
1368
                REQUIRE(durations[i] == Catch::Approx(expected_durations[i]));
4✔
1369
                std::cout << "Neural event " << i << ": Start=" << starts[i]
4✔
1370
                          << ", Duration=" << durations[i] << std::endl;
4✔
1371
            }
1372

1373
        } else {
1✔
UNCOV
1374
            FAIL("Neural event pipeline execution failed: " + pipeline_result.error_message);
×
1375
        }
1376
    }
3✔
1377
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc