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

paulmthompson / WhiskerToolbox / 17846711083

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

push

github

paulmthompson
event in interval computer works with entity ids

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

268 existing lines in 17 files now uncovered.

40247 of 55883 relevant lines covered (72.02%)

1227.29 hits per line

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

95.76
/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 "TimeFrame/interval_data.hpp"
6
#include "IntervalPropertyComputer.h"
7
#include "TimeFrame/TimeFrame.hpp"
8
#include "utils/TableView/core/ExecutionPlan.h"
9
#include "utils/TableView/interfaces/IIntervalSource.h"
10
#include "DataManager.hpp"
11
#include "DigitalTimeSeries/Digital_Interval_Series.hpp"
12
#include "utils/TableView/ComputerRegistry.hpp"
13
#include "utils/TableView/TableRegistry.hpp"
14
#include "utils/TableView/adapters/DataManagerExtension.h"
15
#include "utils/TableView/core/TableView.h"
16
#include "utils/TableView/core/TableViewBuilder.h"
17
#include "utils/TableView/interfaces/IRowSelector.h"
18
#include "utils/TableView/pipeline/TablePipeline.hpp"
19
#include "Entity/EntityGroupManager.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)
UNCOV
258
        : m_name(std::move(name)),
×
259
          m_timeFrame(std::move(timeFrame)),
×
260
          m_intervals(std::move(intervals)) {}
×
261

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

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

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

UNCOV
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 {
UNCOV
280
        std::vector<Interval> result;
×
281

282
        // Convert TimeFrameIndex to time values for comparison
UNCOV
283
        auto startTime = target_timeFrame->getTimeAtIndex(start);
×
UNCOV
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
UNCOV
288
            auto intervalStartTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.start));
×
UNCOV
289
            auto intervalEndTime = m_timeFrame->getTimeAtIndex(TimeFrameIndex(interval.end));
×
290

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

UNCOV
297
        return result;
×
UNCOV
298
    }
×
299

300
private:
301
    std::string m_name;
302
    std::shared_ptr<TimeFrame> m_timeFrame;
303
    std::vector<Interval> m_intervals;
304
};
305

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

308
    SECTION("Start property extraction") {
5✔
309
        // Create time frame
310
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
311
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
312

313
        // Create column intervals (dummy - not used for property extraction)
314
        std::vector<Interval> columnIntervals = {{0, 1}, {3, 5}, {7, 9}};
3✔
315
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
316
                "TestIntervals", timeFrame, columnIntervals);
1✔
317

318
        // Create row intervals
319
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
320
                TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(3)),// Start = 1
321
                TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(5)),// Start = 2
322
                TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(8)),// Start = 6
323
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(1)) // Start = 0
324
        };
3✔
325

326
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
327

328
        // Create the computer for Start property
329
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
330
                                                   IntervalProperty::Start,
331
                                                   "TestIntervals");
3✔
332

333
        // Compute the results
334
        auto results = computer.compute(plan);
1✔
335

336
        // Verify results
337
        REQUIRE(results.size() == 4);
1✔
338
        REQUIRE(results[0] == 1);// First interval start
1✔
339
        REQUIRE(results[1] == 2);// Second interval start
1✔
340
        REQUIRE(results[2] == 6);// Third interval start
1✔
341
        REQUIRE(results[3] == 0);// Fourth interval start
1✔
342
    }
6✔
343

344
    SECTION("End property extraction") {
5✔
345
        // Create time frame
346
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
347
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
348

349
        // Create column intervals (dummy)
350
        std::vector<Interval> columnIntervals = {{0, 1}};
3✔
351
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
352
                "TestIntervals", timeFrame, columnIntervals);
1✔
353

354
        // Create row intervals
355
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
356
                TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(3)),   // End = 3
357
                TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(5)),   // End = 5
358
                TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(8)),   // End = 8
359
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(1))    // End = 1
360
        };
3✔
361

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

364
        // Create the computer for End property
365
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
366
                                                   IntervalProperty::End,
367
                                                   "TestIntervals");
3✔
368

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

372
        // Verify results
373
        REQUIRE(results.size() == 4);
1✔
374
        REQUIRE(results[0] == 3);// First interval end
1✔
375
        REQUIRE(results[1] == 5);// Second interval end
1✔
376
        REQUIRE(results[2] == 8);// Third interval end
1✔
377
        REQUIRE(results[3] == 1);// Fourth interval end
1✔
378
    }
6✔
379

380
    SECTION("Duration property extraction") {
5✔
381
        // Create time frame
382
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
3✔
383
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
384

385
        // Create column intervals (dummy)
386
        std::vector<Interval> columnIntervals = {{0, 1}};
3✔
387
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
388
                "TestIntervals", timeFrame, columnIntervals);
1✔
389

390
        // Create row intervals with different durations
391
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
392
                TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(3)),// Duration = 3-1 = 2
393
                TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(5)),// Duration = 5-2 = 3
394
                TimeFrameInterval(TimeFrameIndex(6), TimeFrameIndex(8)),// Duration = 8-6 = 2
395
                TimeFrameInterval(TimeFrameIndex(0), TimeFrameIndex(6)) // Duration = 6-0 = 6
396
        };
3✔
397

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

400
        // Create the computer for Duration property
401
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
402
                                                   IntervalProperty::Duration,
403
                                                   "TestIntervals");
3✔
404

405
        // Compute the results
406
        auto results = computer.compute(plan);
1✔
407

408
        // Verify results
409
        REQUIRE(results.size() == 4);
1✔
410
        REQUIRE(results[0] == 2);// Duration = 3-1
1✔
411
        REQUIRE(results[1] == 3);// Duration = 5-2
1✔
412
        REQUIRE(results[2] == 2);// Duration = 8-6
1✔
413
        REQUIRE(results[3] == 6);// Duration = 6-0
1✔
414
    }
6✔
415

416
    SECTION("Single interval scenarios") {
5✔
417
        // Create time frame
418
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
419
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
420

421
        // Create column intervals (dummy)
422
        std::vector<Interval> columnIntervals = {{1, 3}};
3✔
423
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
424
                "SingleInterval", timeFrame, columnIntervals);
1✔
425

426
        // Create single row interval
427
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
428
                TimeFrameInterval(TimeFrameIndex(2), TimeFrameIndex(4))// Start=2, End=4, Duration=2
429
        };
3✔
430

431
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
432

433
        // Test Start property
434
        IntervalPropertyComputer<int64_t> startComputer(intervalSource,
1✔
435
                                                        IntervalProperty::Start,
436
                                                        "SingleInterval");
3✔
437
        auto startResults = startComputer.compute(plan);
1✔
438
        REQUIRE(startResults.size() == 1);
1✔
439
        REQUIRE(startResults[0] == 2);
1✔
440

441
        // Test End property
442
        IntervalPropertyComputer<int64_t> endComputer(intervalSource,
1✔
443
                                                      IntervalProperty::End,
444
                                                      "SingleInterval");
3✔
445
        auto endResults = endComputer.compute(plan);
1✔
446
        REQUIRE(endResults.size() == 1);
1✔
447
        REQUIRE(endResults[0] == 4);
1✔
448

449
        // Test Duration property
450
        IntervalPropertyComputer<int64_t> durationComputer(intervalSource,
1✔
451
                                                           IntervalProperty::Duration,
452
                                                           "SingleInterval");
3✔
453
        auto durationResults = durationComputer.compute(plan);
1✔
454
        REQUIRE(durationResults.size() == 1);
1✔
455
        REQUIRE(durationResults[0] == 2);
1✔
456
    }
6✔
457

458
    SECTION("Zero-duration intervals") {
5✔
459
        // Create time frame
460
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
461
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
462

463
        // Create column intervals (dummy)
464
        std::vector<Interval> columnIntervals = {{0, 0}};
3✔
465
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
466
                "ZeroDuration", timeFrame, columnIntervals);
1✔
467

468
        // Create zero-duration row intervals
469
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
470
                TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(1)),// Duration = 0
471
                TimeFrameInterval(TimeFrameIndex(3), TimeFrameIndex(3)) // Duration = 0
472
        };
3✔
473

474
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
475

476
        // Test Duration property
477
        IntervalPropertyComputer<int64_t> durationComputer(intervalSource,
1✔
478
                                                           IntervalProperty::Duration,
479
                                                           "ZeroDuration");
3✔
480
        auto durationResults = durationComputer.compute(plan);
1✔
481

482
        REQUIRE(durationResults.size() == 2);
1✔
483
        REQUIRE(durationResults[0] == 0);// Zero duration
1✔
484
        REQUIRE(durationResults[1] == 0);// Zero duration
1✔
485
    }
6✔
486
}
5✔
487

488
TEST_CASE("DM - TV - IntervalPropertyComputer Error Handling", "[IntervalPropertyComputer][Error]") {
1✔
489

490
    SECTION("ExecutionPlan without intervals throws exception") {
1✔
491
        // Create time frame
492
        std::vector<int> timeValues = {0, 1, 2, 3, 4, 5};
3✔
493
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
494

495
        // Create column intervals (dummy)
496
        std::vector<Interval> columnIntervals = {{1, 3}};
3✔
497
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
498
                "TestIntervals", timeFrame, columnIntervals);
1✔
499

500
        // Create execution plan with indices instead of intervals
501
        std::vector<TimeFrameIndex> indices = {TimeFrameIndex(0), TimeFrameIndex(1)};
3✔
502
        ExecutionPlan plan(indices, timeFrame);
1✔
503

504
        // Create the computer
505
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
506
                                                   IntervalProperty::Start,
507
                                                   "TestIntervals");
3✔
508

509
        // Should throw an exception
510
        REQUIRE_THROWS_AS(computer.compute(plan), std::runtime_error);
2✔
511
    }
2✔
512
}
1✔
513

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

516
    SECTION("Test with different numeric types") {
1✔
517
        // Create time frame
518
        std::vector<int> timeValues = {0, 5, 10, 15, 20, 25};
3✔
519
        auto timeFrame = std::make_shared<TimeFrame>(timeValues);
1✔
520

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

526
        // Create row intervals
527
        std::vector<TimeFrameInterval> rowIntervals = {
1✔
528
                TimeFrameInterval(TimeFrameIndex(1), TimeFrameIndex(4))// Start=1, End=4, Duration=3
529
        };
3✔
530

531
        ExecutionPlan plan(rowIntervals, timeFrame);
1✔
532

533
        // Test with int64_t
534
        IntervalPropertyComputer<int64_t> intComputer(intervalSource,
1✔
535
                                                      IntervalProperty::Duration,
536
                                                      "TestIntervals");
3✔
537
        auto intResults = intComputer.compute(plan);
1✔
538
        REQUIRE(intResults.size() == 1);
1✔
539
        REQUIRE(intResults[0] == 3);
1✔
540

541
        // Test with float
542
        IntervalPropertyComputer<float> floatComputer(intervalSource,
1✔
543
                                                      IntervalProperty::Duration,
544
                                                      "TestIntervals");
3✔
545
        auto floatResults = floatComputer.compute(plan);
1✔
546
        REQUIRE(floatResults.size() == 1);
1✔
547
        REQUIRE(floatResults[0] == Catch::Approx(3.0f));
1✔
548

549
        // Test with double
550
        IntervalPropertyComputer<double> doubleComputer(intervalSource,
1✔
551
                                                        IntervalProperty::Duration,
552
                                                        "TestIntervals");
3✔
553
        auto doubleResults = doubleComputer.compute(plan);
1✔
554
        REQUIRE(doubleResults.size() == 1);
1✔
555
        REQUIRE(doubleResults[0] == Catch::Approx(3.0));
1✔
556
    }
2✔
557
}
1✔
558

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

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

566
        std::vector<Interval> columnIntervals = {{0, 1}};
3✔
567
        auto intervalSource = std::make_shared<MockIntervalSource>(
1✔
568
                "TestSource", timeFrame, columnIntervals);
1✔
569

570
        // Create computer
571
        IntervalPropertyComputer<int64_t> computer(intervalSource,
1✔
572
                                                   IntervalProperty::Start,
573
                                                   "TestSourceName");
3✔
574

575
        // Test source dependency
576
        REQUIRE(computer.getSourceDependency() == "TestSourceName");
1✔
577
    }
2✔
578
}
1✔
579

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

582
    SECTION("Test with behavior intervals from fixture") {
2✔
583
        auto & dm = getDataManager();
1✔
584
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
585

586
        // Get the interval source from the DataManager
587
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
588

589
        REQUIRE(behavior_source != nullptr);
1✔
590

591
        // Create row selector from behavior intervals
592
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
593
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
594
                TimeFrameIndex(0), TimeFrameIndex(200), behavior_time_frame.get());
1✔
595

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

598
        // Convert to TimeFrameIntervals for row selector
599
        std::vector<TimeFrameInterval> row_intervals;
1✔
600
        for (auto const & interval: behavior_intervals) {
6✔
601
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
5✔
602
        }
603

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

606
        // Create TableView builder
607
        TableViewBuilder builder(dme);
1✔
608
        builder.setRowSelector(std::move(row_selector));
1✔
609

610
        // Test Start property
611
        builder.addColumn<double>("IntervalStart",
5✔
612
                                  std::make_unique<IntervalPropertyComputer<double>>(behavior_source,
3✔
613
                                                                                     IntervalProperty::Start, "BehaviorPeriods"));
2✔
614

615
        // Test End property
616
        builder.addColumn<double>("IntervalEnd",
5✔
617
                                  std::make_unique<IntervalPropertyComputer<double>>(behavior_source,
3✔
618
                                                                                     IntervalProperty::End, "BehaviorPeriods"));
2✔
619

620
        // Test Duration property
621
        builder.addColumn<double>("IntervalDuration",
5✔
622
                                  std::make_unique<IntervalPropertyComputer<double>>(behavior_source,
3✔
623
                                                                                     IntervalProperty::Duration, "BehaviorPeriods"));
2✔
624

625
        // Build the table
626
        TableView table = builder.build();
1✔
627

628
        // Verify table structure
629
        REQUIRE(table.getRowCount() == 5);
1✔
630
        REQUIRE(table.getColumnCount() == 3);
1✔
631
        REQUIRE(table.hasColumn("IntervalStart"));
3✔
632
        REQUIRE(table.hasColumn("IntervalEnd"));
3✔
633
        REQUIRE(table.hasColumn("IntervalDuration"));
3✔
634

635
        // Get the column data
636
        auto starts = table.getColumnValues<double>("IntervalStart");
3✔
637
        auto ends = table.getColumnValues<double>("IntervalEnd");
3✔
638
        auto durations = table.getColumnValues<double>("IntervalDuration");
3✔
639

640
        REQUIRE(starts.size() == 5);
1✔
641
        REQUIRE(ends.size() == 5);
1✔
642
        REQUIRE(durations.size() == 5);
1✔
643

644
        // Verify the property values match our test data expectations
645
        // Expected behavior periods from fixture:
646
        // Period 1: 10-15 (duration = 5)
647
        // Period 2: 30-50 (duration = 20)
648
        // Period 3: 70-120 (duration = 50)
649
        // Period 4: 150-155 (duration = 5)
650
        // Period 5: 160-190 (duration = 30)
651

652
        for (size_t i = 0; i < 5; ++i) {
6✔
653
            REQUIRE(starts[i] >= 0);                       // Valid start times
5✔
654
            REQUIRE(ends[i] > starts[i]);                  // End > Start
5✔
655
            REQUIRE(durations[i] > 0);                     // Positive durations
5✔
656
            REQUIRE(durations[i] == (ends[i] - starts[i]));// Duration = End - Start
5✔
657

658
            std::cout << "Behavior period " << i << ": Start=" << starts[i]
5✔
659
                      << ", End=" << ends[i] << ", Duration=" << durations[i] << std::endl;
5✔
660
        }
661

662
        // Verify specific expected values (first few intervals)
663
        REQUIRE(starts[0] == Catch::Approx(10.0));  // First period starts at 10
1✔
664
        REQUIRE(ends[0] == Catch::Approx(15.0));    // First period ends at 15
1✔
665
        REQUIRE(durations[0] == Catch::Approx(5.0));// First period duration = 5
1✔
666

667
        REQUIRE(starts[1] == Catch::Approx(30.0));   // Second period starts at 30
1✔
668
        REQUIRE(durations[1] == Catch::Approx(20.0));// Second period duration = 20
1✔
669
    }
3✔
670

671
    SECTION("Test with neural events from fixture") {
2✔
672
        auto & dm = getDataManager();
1✔
673
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
674

675
        // Get neural events from high-resolution timeframe
676
        auto neural_source = dme->getIntervalSource("NeuralEvents");
3✔
677
        REQUIRE(neural_source != nullptr);
1✔
678

679
        auto neural_time_frame = dm.getTime(TimeKey("high_res_time"));
1✔
680
        auto neural_intervals = neural_source->getIntervalsInRange(
1✔
681
                TimeFrameIndex(0), TimeFrameIndex(200), neural_time_frame.get());
1✔
682

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

685
        // Convert to TimeFrameIntervals for row selector
686
        std::vector<TimeFrameInterval> row_intervals;
1✔
687
        for (auto const & interval: neural_intervals) {
5✔
688
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
4✔
689
        }
690

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

693
        TableViewBuilder builder(dme);
1✔
694
        builder.setRowSelector(std::move(row_selector));
1✔
695

696
        // Add property computers
697
        builder.addColumn<int64_t>("EventStart",
5✔
698
                                   std::make_unique<IntervalPropertyComputer<int64_t>>(neural_source,
3✔
699
                                                                                       IntervalProperty::Start, "NeuralEvents"));
2✔
700

701
        builder.addColumn<int64_t>("EventDuration",
5✔
702
                                   std::make_unique<IntervalPropertyComputer<int64_t>>(neural_source,
3✔
703
                                                                                       IntervalProperty::Duration, "NeuralEvents"));
2✔
704

705
        // Build and verify the table
706
        TableView table = builder.build();
1✔
707

708
        REQUIRE(table.getRowCount() == 4);
1✔
709
        REQUIRE(table.getColumnCount() == 2);
1✔
710

711
        auto starts = table.getColumnValues<int64_t>("EventStart");
3✔
712
        auto durations = table.getColumnValues<int64_t>("EventDuration");
3✔
713

714
        REQUIRE(starts.size() == 4);
1✔
715
        REQUIRE(durations.size() == 4);
1✔
716

717
        // Verify neural events match our test data:
718
        // Event 1: 12-13 (duration = 1)
719
        // Event 2: 45-47 (duration = 2)
720
        // Event 3: 89-92 (duration = 3)
721
        // Event 4: 156-161 (duration = 5)
722

723
        std::vector<int64_t> expected_starts = {12, 45, 89, 156};
3✔
724
        std::vector<int64_t> expected_durations = {1, 2, 3, 5};
3✔
725

726
        for (size_t i = 0; i < 4; ++i) {
5✔
727
            REQUIRE(starts[i] == expected_starts[i]);
4✔
728
            REQUIRE(durations[i] == expected_durations[i]);
4✔
729

730
            std::cout << "Neural event " << i << ": Start=" << starts[i]
4✔
731
                      << ", Duration=" << durations[i] << std::endl;
4✔
732
        }
733
    }
3✔
734
}
2✔
735

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

738
    SECTION("TableView creation and EntityID extraction with IntervalPropertyComputer") {
1✔
739
        auto & dm = getDataManager();
1✔
740
        
741
        // Create DataManagerExtension for TableView integration
742
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
743
        
744
        // Get behavior intervals from our test fixture
745
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
746
        REQUIRE(behavior_source != nullptr);
1✔
747
        
748
        // Create row selector for behavior intervals
749
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
750
        auto behavior_intervals = behavior_source->getIntervalsInRange(
1✔
751
            TimeFrameIndex(0), TimeFrameIndex(200), behavior_time_frame.get());
1✔
752
        
753
        REQUIRE(behavior_intervals.size() == 5); // 5 behavior periods from fixture
1✔
754
        
755
        // Convert to TimeFrameIntervals for row selector
756
        std::vector<TimeFrameInterval> row_intervals;
1✔
757
        for (auto const & interval : behavior_intervals) {
6✔
758
            row_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
5✔
759
        }
760
        
761
        auto row_selector = std::make_unique<IntervalSelector>(row_intervals, behavior_time_frame);
1✔
762
        
763
        // Build TableView using TableViewBuilder
764
        TableViewBuilder builder(dme);
1✔
765
        builder.setRowSelector(std::move(row_selector));
1✔
766
        
767
        // Add IntervalPropertyComputer columns
768
        builder.addColumn<double>("IntervalStart", 
5✔
769
            std::make_unique<IntervalPropertyComputer<double>>(behavior_source, 
3✔
770
                                                               IntervalProperty::Start, "BehaviorPeriods"));
2✔
771
        builder.addColumn<double>("IntervalDuration", 
5✔
772
            std::make_unique<IntervalPropertyComputer<double>>(behavior_source, 
3✔
773
                                                               IntervalProperty::Duration, "BehaviorPeriods"));
2✔
774
        
775
        auto table = builder.build();
1✔
776
        
777
        // Verify table structure
778
        REQUIRE(table.getRowCount() == 5); // 5 behavior periods
1✔
779
        REQUIRE(table.getColumnCount() == 2);
1✔
780
        REQUIRE(table.hasColumn("IntervalStart"));
3✔
781
        REQUIRE(table.hasColumn("IntervalDuration"));
3✔
782
        
783
        // Verify EntityID information is available for IntervalPropertyComputer columns
784
        REQUIRE(table.hasColumnEntityIds("IntervalStart"));
3✔
785
        REQUIRE(table.hasColumnEntityIds("IntervalDuration"));
3✔
786
        
787
        // Get EntityIDs from the columns (all IntervalPropertyComputer columns should share the same EntityIDs)
788
        auto start_entity_ids_variant = table.getColumnEntityIds("IntervalStart");
3✔
789
        auto duration_entity_ids_variant = table.getColumnEntityIds("IntervalDuration");
3✔
790
        
791
        // IntervalPropertyComputer uses Simple EntityID structure, so extract std::vector<EntityId>
792
        REQUIRE(std::holds_alternative<std::vector<EntityId>>(start_entity_ids_variant));
1✔
793
        REQUIRE(std::holds_alternative<std::vector<EntityId>>(duration_entity_ids_variant));
1✔
794
        
795
        auto start_entity_ids = std::get<std::vector<EntityId>>(start_entity_ids_variant);
1✔
796
        auto duration_entity_ids = std::get<std::vector<EntityId>>(duration_entity_ids_variant);
1✔
797
        
798
        REQUIRE(start_entity_ids.size() == 5); // Should match row count
1✔
799
        REQUIRE(duration_entity_ids.size() == 5);
1✔
800
        
801
        // Verify all EntityIDs are valid (non-zero)
802
        for (EntityId id : start_entity_ids) {
6✔
803
            REQUIRE(id != 0);
5✔
804
            INFO("Start Column EntityID: " << id);
5✔
805
        }
5✔
806
        
807
        // Verify that both columns have the same EntityIDs (they come from the same intervals)
808
        REQUIRE(duration_entity_ids == start_entity_ids);
1✔
809
        
810
        // Get sample data from table columns
811
        auto start_values = table.getColumnValues<double>("IntervalStart");
3✔
812
        auto duration_values = table.getColumnValues<double>("IntervalDuration");
3✔
813
        
814
        REQUIRE(start_values.size() == 5);
1✔
815
        REQUIRE(duration_values.size() == 5);
1✔
816
        
817
        // Verify data consistency and entity relationships
818
        for (size_t i = 0; i < 5; ++i) {
6✔
819
            // Verify basic properties
820
            REQUIRE(start_values[i] >= 0);
5✔
821
            REQUIRE(duration_values[i] > 0);
5✔
822
            
823
            INFO("Row " << i << ": Start=" << start_values[i] 
5✔
824
                 << ", Duration=" << duration_values[i] 
825
                 << ", EntityID=" << start_entity_ids[i]);
826
        }
5✔
827
        
828
        // Test EntityID round-trip: Compare TableView EntityIDs with original source EntityIDs
829
        INFO("Testing EntityID round-trip from source data to TableView");
1✔
830
        
831
        // Get the original DigitalIntervalSeries data that was used to create the intervals
832
        auto behavior_data = dm.getData<DigitalIntervalSeries>("BehaviorPeriods");
3✔
833
        REQUIRE(behavior_data != nullptr);
1✔
834
        
835
        // Get the EntityIDs directly from the source data
836
        auto source_entity_ids = behavior_data->getEntityIds();
1✔
837
        REQUIRE(source_entity_ids.size() == 5); // Should match the number of intervals
1✔
838
        
839
        // Debug: Print source EntityIDs to see if they're valid
840
        INFO("Source EntityIDs from DigitalIntervalSeries:");
1✔
841
        for (size_t i = 0; i < source_entity_ids.size(); ++i) {
6✔
842
            INFO("  Source EntityID[" << i << "] = " << source_entity_ids[i]);
5✔
843
            REQUIRE(source_entity_ids[i] != 0); // Verify source EntityIDs are valid
5✔
844
        }
5✔
845
        
846
        // Verify that TableView EntityIDs match the source EntityIDs
847
        REQUIRE(start_entity_ids.size() == source_entity_ids.size());
1✔
848
        for (size_t i = 0; i < source_entity_ids.size(); ++i) {
6✔
849
            REQUIRE(start_entity_ids[i] == source_entity_ids[i]);
5✔
850
            INFO("EntityID " << i << ": Source=" << source_entity_ids[i] 
5✔
851
                 << ", TableView=" << start_entity_ids[i] << " ✓");
852
        }
5✔
853
        
854
        INFO("✓ EntityID round-trip successful: " << start_entity_ids.size() << " EntityIDs match source data");
1✔
855
        
856
        // Test individual row EntityID extraction
857
        for (size_t row_idx = 0; row_idx < 5; ++row_idx) {
6✔
858
            // Get EntityIDs for a specific row
859
            auto row_start_ids = table.getCellEntityIds("IntervalStart", row_idx);
15✔
860
            auto row_duration_ids = table.getCellEntityIds("IntervalDuration", row_idx);
15✔
861
            
862
            REQUIRE(row_start_ids.size() == 1); // Should have exactly one EntityID per interval
5✔
863
            REQUIRE(row_duration_ids.size() == 1);
5✔
864
            
865
            // Verify they match the overall EntityID list
866
            REQUIRE(row_start_ids[0] == start_entity_ids[row_idx]);
5✔
867
            REQUIRE(row_duration_ids[0] == start_entity_ids[row_idx]);
5✔
868
            
869
            INFO("Row " << row_idx << " individual EntityID check: " << row_start_ids[0]);
5✔
870
        }
5✔
871
        
872
        INFO("✓ All IntervalPropertyComputer EntityID extraction tests passed");
1✔
873
    }
2✔
874
}
1✔
875

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

878
    SECTION("Verify IntervalPropertyComputer is registered in ComputerRegistry") {
3✔
879
        auto & registry = getTableRegistry().getComputerRegistry();
1✔
880

881
        // Check that all interval property computers are registered
882
        auto start_info = registry.findComputerInfo("Interval Start");
3✔
883
        auto end_info = registry.findComputerInfo("Interval End");
3✔
884
        auto duration_info = registry.findComputerInfo("Interval Duration");
3✔
885

886
        REQUIRE(start_info != nullptr);
1✔
887
        REQUIRE(end_info != nullptr);
1✔
888
        REQUIRE(duration_info != nullptr);
1✔
889

890
        // Verify computer info details for Start
891
        REQUIRE(start_info->name == "Interval Start");
1✔
892
        REQUIRE(start_info->outputType == typeid(double));
1✔
893
        REQUIRE(start_info->outputTypeName == "double");
1✔
894
        REQUIRE(start_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
895
        REQUIRE(start_info->requiredSourceType == typeid(std::shared_ptr<IIntervalSource>));
1✔
896

897
        // Verify computer info details for End
898
        REQUIRE(end_info->name == "Interval End");
1✔
899
        REQUIRE(end_info->outputType == typeid(double));
1✔
900
        REQUIRE(end_info->outputTypeName == "double");
1✔
901
        REQUIRE(end_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
902
        REQUIRE(end_info->requiredSourceType == typeid(std::shared_ptr<IIntervalSource>));
1✔
903

904
        // Verify computer info details for Duration
905
        REQUIRE(duration_info->name == "Interval Duration");
1✔
906
        REQUIRE(duration_info->outputType == typeid(double));
1✔
907
        REQUIRE(duration_info->outputTypeName == "double");
1✔
908
        REQUIRE(duration_info->requiredRowSelector == RowSelectorType::IntervalBased);
1✔
909
        REQUIRE(duration_info->requiredSourceType == typeid(std::shared_ptr<IIntervalSource>));
1✔
910
    }
3✔
911

912
    SECTION("Create IntervalPropertyComputer via ComputerRegistry") {
3✔
913
        auto & dm = getDataManager();
1✔
914
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
915
        auto & registry = getTableRegistry().getComputerRegistry();
1✔
916

917
        // Get behavior source for testing
918
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
919
        REQUIRE(behavior_source != nullptr);
1✔
920

921
        // Create computers via registry
922
        std::map<std::string, std::string> empty_params;
1✔
923

924
        auto start_computer = registry.createTypedComputer<double>(
1✔
925
                "Interval Start", behavior_source, empty_params);
3✔
926
        auto end_computer = registry.createTypedComputer<double>(
1✔
927
                "Interval End", behavior_source, empty_params);
3✔
928
        auto duration_computer = registry.createTypedComputer<double>(
1✔
929
                "Interval Duration", behavior_source, empty_params);
3✔
930

931
        REQUIRE(start_computer != nullptr);
1✔
932
        REQUIRE(end_computer != nullptr);
1✔
933
        REQUIRE(duration_computer != nullptr);
1✔
934

935
        // Test that the created computers work correctly
936
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
937

938
        // Create a simple test interval
939
        std::vector<TimeFrameInterval> test_intervals = {
1✔
940
                TimeFrameInterval(TimeFrameIndex(30), TimeFrameIndex(50))// Behavior period 2
941
        };
3✔
942

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

945
        TableViewBuilder builder(dme);
1✔
946
        builder.setRowSelector(std::move(row_selector));
1✔
947

948
        // Use the registry-created computers
949
        builder.addColumn("RegistryStart", std::move(start_computer));
3✔
950
        builder.addColumn("RegistryEnd", std::move(end_computer));
3✔
951
        builder.addColumn("RegistryDuration", std::move(duration_computer));
3✔
952

953
        // Build and verify the table
954
        TableView table = builder.build();
1✔
955

956
        REQUIRE(table.getRowCount() == 1);
1✔
957
        REQUIRE(table.getColumnCount() == 3);
1✔
958
        REQUIRE(table.hasColumn("RegistryStart"));
3✔
959
        REQUIRE(table.hasColumn("RegistryEnd"));
3✔
960
        REQUIRE(table.hasColumn("RegistryDuration"));
3✔
961

962
        auto starts = table.getColumnValues<double>("RegistryStart");
3✔
963
        auto ends = table.getColumnValues<double>("RegistryEnd");
3✔
964
        auto durations = table.getColumnValues<double>("RegistryDuration");
3✔
965

966
        REQUIRE(starts.size() == 1);
1✔
967
        REQUIRE(ends.size() == 1);
1✔
968
        REQUIRE(durations.size() == 1);
1✔
969

970
        // Verify reasonable results (should match the test interval: 30-50)
971
        REQUIRE(starts[0] == Catch::Approx(30.0));
1✔
972
        REQUIRE(ends[0] == Catch::Approx(50.0));
1✔
973
        REQUIRE(durations[0] == Catch::Approx(20.0));
1✔
974

975
        std::cout << "Registry test - Start: " << starts[0]
1✔
976
                  << ", End: " << ends[0] << ", Duration: " << durations[0] << std::endl;
1✔
977
    }
4✔
978

979
    SECTION("Compare registry-created vs direct-created computers") {
3✔
980
        auto & dm = getDataManager();
1✔
981
        auto dme = std::make_shared<DataManagerExtension>(dm);
1✔
982
        auto & registry = getTableRegistry().getComputerRegistry();
1✔
983

984
        auto behavior_source = dme->getIntervalSource("BehaviorPeriods");
3✔
985
        REQUIRE(behavior_source != nullptr);
1✔
986

987
        // Create computer via registry
988
        std::map<std::string, std::string> empty_params;
1✔
989
        auto registry_computer = registry.createTypedComputer<double>(
1✔
990
                "Interval Duration", behavior_source, empty_params);
3✔
991

992
        // Create computer directly
993
        auto direct_computer = std::make_unique<IntervalPropertyComputer<double>>(
1✔
994
                behavior_source, IntervalProperty::Duration, "BehaviorPeriods");
1✔
995

996
        REQUIRE(registry_computer != nullptr);
1✔
997
        REQUIRE(direct_computer != nullptr);
1✔
998

999
        // Test both computers with the same data
1000
        auto behavior_time_frame = dm.getTime(TimeKey("behavior_time"));
1✔
1001
        std::vector<TimeFrameInterval> test_intervals = {
1✔
1002
                TimeFrameInterval(TimeFrameIndex(70), TimeFrameIndex(120))// Period 3: duration = 50
1003
        };
3✔
1004

1005
        ExecutionPlan plan(test_intervals, behavior_time_frame);
1✔
1006

1007
        auto registry_result = registry_computer->compute(plan);
1✔
1008
        auto direct_result = direct_computer->compute(plan);
1✔
1009

1010
        REQUIRE(registry_result.size() == 1);
1✔
1011
        REQUIRE(direct_result.size() == 1);
1✔
1012

1013
        // Results should be identical
1014
        REQUIRE(registry_result[0] == Catch::Approx(direct_result[0]));
1✔
1015
        REQUIRE(registry_result[0] == Catch::Approx(50.0));// Expected duration
1✔
1016

1017
        std::cout << "Comparison test - Registry result: " << registry_result[0]
1✔
1018
                  << ", Direct result: " << direct_result[0] << std::endl;
1✔
1019
    }
4✔
1020
}
3✔
1021

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

1024
    SECTION("Test all property operations via JSON pipeline") {
2✔
1025
        // JSON configuration for testing IntervalPropertyComputer through TablePipeline
1026
        char const * json_config = R"({
1✔
1027
            "metadata": {
1028
                "name": "Interval Property Test",
1029
                "description": "Test JSON execution of IntervalPropertyComputer",
1030
                "version": "1.0"
1031
            },
1032
            "tables": [
1033
                {
1034
                    "table_id": "interval_property_test",
1035
                    "name": "Interval Property Test Table",
1036
                    "description": "Test table using IntervalPropertyComputer",
1037
                    "row_selector": {
1038
                        "type": "interval",
1039
                        "source": "BehaviorPeriods"
1040
                    },
1041
                    "columns": [
1042
                        {
1043
                            "name": "IntervalStartTime",
1044
                            "description": "Start time of each behavior period",
1045
                            "data_source": "BehaviorPeriods",
1046
                            "computer": "Interval Start"
1047
                        },
1048
                        {
1049
                            "name": "IntervalEndTime",
1050
                            "description": "End time of each behavior period",
1051
                            "data_source": "BehaviorPeriods",
1052
                            "computer": "Interval End"
1053
                        },
1054
                        {
1055
                            "name": "IntervalDuration",
1056
                            "description": "Duration of each behavior period",
1057
                            "data_source": "BehaviorPeriods",
1058
                            "computer": "Interval Duration"
1059
                        }
1060
                    ]
1061
                }
1062
            ]
1063
        })";
1064

1065
        auto & pipeline = getTablePipeline();
1✔
1066

1067
        // Parse the JSON configuration
1068
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1069

1070
        // Load configuration into pipeline
1071
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1072
        REQUIRE(load_success);
1✔
1073

1074
        // Verify configuration was loaded correctly
1075
        auto table_configs = pipeline.getTableConfigurations();
1✔
1076
        REQUIRE(table_configs.size() == 1);
1✔
1077

1078
        auto const & config = table_configs[0];
1✔
1079
        REQUIRE(config.table_id == "interval_property_test");
1✔
1080
        REQUIRE(config.name == "Interval Property Test Table");
1✔
1081
        REQUIRE(config.columns.size() == 3);
1✔
1082

1083
        // Verify column configurations
1084
        auto const & column1 = config.columns[0];
1✔
1085
        REQUIRE(column1["name"] == "IntervalStartTime");
1✔
1086
        REQUIRE(column1["computer"] == "Interval Start");
1✔
1087
        REQUIRE(column1["data_source"] == "BehaviorPeriods");
1✔
1088

1089
        auto const & column2 = config.columns[1];
1✔
1090
        REQUIRE(column2["name"] == "IntervalEndTime");
1✔
1091
        REQUIRE(column2["computer"] == "Interval End");
1✔
1092
        REQUIRE(column2["data_source"] == "BehaviorPeriods");
1✔
1093

1094
        auto const & column3 = config.columns[2];
1✔
1095
        REQUIRE(column3["name"] == "IntervalDuration");
1✔
1096
        REQUIRE(column3["computer"] == "Interval Duration");
1✔
1097
        REQUIRE(column3["data_source"] == "BehaviorPeriods");
1✔
1098

1099
        // Verify row selector configuration
1100
        REQUIRE(config.row_selector["type"] == "interval");
1✔
1101
        REQUIRE(config.row_selector["source"] == "BehaviorPeriods");
1✔
1102

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

1105
        // Execute the pipeline
1106
        auto pipeline_result = pipeline.execute([](int table_index, std::string const & table_name, int table_progress, int overall_progress) {
2✔
1107
            std::cout << "Building table " << table_index << " (" << table_name << "): "
5✔
1108
                      << table_progress << "% (Overall: " << overall_progress << "%)" << std::endl;
5✔
1109
        });
2✔
1110

1111
        if (pipeline_result.success) {
1✔
1112
            std::cout << "Pipeline executed successfully!" << std::endl;
1✔
1113
            std::cout << "Tables completed: " << pipeline_result.tables_completed << "/" << pipeline_result.total_tables << std::endl;
1✔
1114
            std::cout << "Execution time: " << pipeline_result.total_execution_time_ms << " ms" << std::endl;
1✔
1115

1116
            // Verify the built table exists
1117
            auto & registry = getTableRegistry();
1✔
1118
            REQUIRE(registry.hasTable("interval_property_test"));
3✔
1119

1120
            // Get the built table and verify its structure
1121
            auto built_table = registry.getBuiltTable("interval_property_test");
3✔
1122
            REQUIRE(built_table != nullptr);
1✔
1123

1124
            // Check that the table has the expected columns
1125
            auto column_names = built_table->getColumnNames();
1✔
1126
            std::cout << "Built table has " << column_names.size() << " columns" << std::endl;
1✔
1127
            for (auto const & name: column_names) {
4✔
1128
                std::cout << "  Column: " << name << std::endl;
3✔
1129
            }
1130

1131
            REQUIRE(column_names.size() == 3);
1✔
1132
            REQUIRE(built_table->hasColumn("IntervalStartTime"));
3✔
1133
            REQUIRE(built_table->hasColumn("IntervalEndTime"));
3✔
1134
            REQUIRE(built_table->hasColumn("IntervalDuration"));
3✔
1135

1136
            // Verify table has 5 rows (one for each behavior period)
1137
            REQUIRE(built_table->getRowCount() == 5);
1✔
1138

1139
            // Get and verify the computed values
1140
            auto start_times = built_table->getColumnValues<double>("IntervalStartTime");
3✔
1141
            auto end_times = built_table->getColumnValues<double>("IntervalEndTime");
3✔
1142
            auto durations = built_table->getColumnValues<double>("IntervalDuration");
3✔
1143

1144
            REQUIRE(start_times.size() == 5);
1✔
1145
            REQUIRE(end_times.size() == 5);
1✔
1146
            REQUIRE(durations.size() == 5);
1✔
1147

1148
            for (size_t i = 0; i < 5; ++i) {
6✔
1149
                REQUIRE(start_times[i] >= 0);                                         // Valid start times
5✔
1150
                REQUIRE(end_times[i] > start_times[i]);                               // End > Start
5✔
1151
                REQUIRE(durations[i] > 0);                                            // Positive durations
5✔
1152
                REQUIRE(durations[i] == Catch::Approx(end_times[i] - start_times[i]));// Duration = End - Start
5✔
1153

1154
                std::cout << "Row " << i << ": Start=" << start_times[i]
5✔
1155
                          << ", End=" << end_times[i] << ", Duration=" << durations[i] << std::endl;
5✔
1156
            }
1157

1158
            // Verify specific expected values from our fixture
1159
            REQUIRE(start_times[0] == Catch::Approx(10.0));// First period: 10-15
1✔
1160
            REQUIRE(end_times[0] == Catch::Approx(15.0));
1✔
1161
            REQUIRE(durations[0] == Catch::Approx(5.0));
1✔
1162

1163
            REQUIRE(start_times[1] == Catch::Approx(30.0));// Second period: 30-50
1✔
1164
            REQUIRE(durations[1] == Catch::Approx(20.0));
1✔
1165

1166
        } else {
1✔
UNCOV
1167
            std::cout << "Pipeline execution failed: " << pipeline_result.error_message << std::endl;
×
UNCOV
1168
            FAIL("Pipeline execution failed: " + pipeline_result.error_message);
×
1169
        }
1170
    }
3✔
1171

1172
    SECTION("Test mixed interval properties with neural data") {
2✔
1173
        char const * json_config = R"({
1✔
1174
            "metadata": {
1175
                "name": "Neural Event Property Test",
1176
                "description": "Test IntervalPropertyComputer with neural events"
1177
            },
1178
            "tables": [
1179
                {
1180
                    "table_id": "neural_property_test",
1181
                    "name": "Neural Event Property Table",
1182
                    "description": "Test table using neural event intervals",
1183
                    "row_selector": {
1184
                        "type": "interval",
1185
                        "source": "NeuralEvents"
1186
                    },
1187
                    "columns": [
1188
                        {
1189
                            "name": "EventStart",
1190
                            "description": "Start time of neural events",
1191
                            "data_source": "NeuralEvents",
1192
                            "computer": "Interval Start"
1193
                        },
1194
                        {
1195
                            "name": "EventDuration",
1196
                            "description": "Duration of neural events",
1197
                            "data_source": "NeuralEvents",
1198
                            "computer": "Interval Duration"
1199
                        }
1200
                    ]
1201
                }
1202
            ]
1203
        })";
1204

1205
        auto & pipeline = getTablePipeline();
1✔
1206
        nlohmann::json json_obj = nlohmann::json::parse(json_config);
1✔
1207

1208
        bool load_success = pipeline.loadFromJson(json_obj);
1✔
1209
        REQUIRE(load_success);
1✔
1210

1211
        auto table_configs = pipeline.getTableConfigurations();
1✔
1212
        REQUIRE(table_configs.size() == 1);
1✔
1213

1214
        auto const & config = table_configs[0];
1✔
1215
        REQUIRE(config.columns.size() == 2);
1✔
1216
        REQUIRE(config.columns[0]["computer"] == "Interval Start");
1✔
1217
        REQUIRE(config.columns[1]["computer"] == "Interval Duration");
1✔
1218

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

1221
        // Execute the pipeline
1222
        auto pipeline_result = pipeline.execute();
1✔
1223

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

1227
            // Verify the built table exists and has correct structure
1228
            auto & registry = getTableRegistry();
1✔
1229
            REQUIRE(registry.hasTable("neural_property_test"));
3✔
1230

1231
            auto built_table = registry.getBuiltTable("neural_property_test");
3✔
1232
            REQUIRE(built_table != nullptr);
1✔
1233

1234
            // Should have 4 rows (one for each neural event from our fixture)
1235
            REQUIRE(built_table->getRowCount() == 4);
1✔
1236
            REQUIRE(built_table->getColumnCount() == 2);
1✔
1237
            REQUIRE(built_table->hasColumn("EventStart"));
3✔
1238
            REQUIRE(built_table->hasColumn("EventDuration"));
3✔
1239

1240
            auto starts = built_table->getColumnValues<double>("EventStart");
3✔
1241
            auto durations = built_table->getColumnValues<double>("EventDuration");
3✔
1242

1243
            REQUIRE(starts.size() == 4);
1✔
1244
            REQUIRE(durations.size() == 4);
1✔
1245

1246
            // Expected neural events from fixture:
1247
            // Event 1: 12-13 (duration = 1)
1248
            // Event 2: 45-47 (duration = 2)
1249
            // Event 3: 89-92 (duration = 3)
1250
            // Event 4: 156-161 (duration = 5)
1251
            std::vector<double> expected_starts = {12.0, 45.0, 89.0, 156.0};
3✔
1252
            std::vector<double> expected_durations = {1.0, 2.0, 3.0, 5.0};
3✔
1253

1254
            for (size_t i = 0; i < 4; ++i) {
5✔
1255
                REQUIRE(starts[i] == Catch::Approx(expected_starts[i]));
4✔
1256
                REQUIRE(durations[i] == Catch::Approx(expected_durations[i]));
4✔
1257
                std::cout << "Neural event " << i << ": Start=" << starts[i]
4✔
1258
                          << ", Duration=" << durations[i] << std::endl;
4✔
1259
            }
1260

1261
        } else {
1✔
UNCOV
1262
            FAIL("Neural event pipeline execution failed: " + pipeline_result.error_message);
×
1263
        }
1264
    }
3✔
1265
}
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