Skip to content

Commit fd57058

Browse files
committed
ENH: Add ForceOocAlgorithm flag to test both algorithm paths in in-core builds
DispatchAlgorithm now checks a static ForceOocAlgorithm() flag in addition to array storage type. Tests use ForceOocAlgorithmGuard with Catch2 GENERATE to exercise both BFS and CCL paths regardless of build configuration. Signed-off-by: Joey Kleingers <[email protected]>
1 parent 5d72fa9 commit fd57058

File tree

3 files changed

+105
-8
lines changed

3 files changed

+105
-8
lines changed

docs/AlgorithmDispatch.md

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ template <typename InCoreAlgo, typename OocAlgo, typename... ArgsT>
7979
Result<> DispatchAlgorithm(std::initializer_list<const IDataArray*> arrays, ArgsT&&... args);
8080
```
8181
82-
Checks whether any array in `arrays` uses OOC storage. If so, constructs `OocAlgo(args...)` and calls `operator()()`. Otherwise, constructs `InCoreAlgo(args...)` and calls `operator()()`.
82+
Checks whether any array in `arrays` uses OOC storage, or if the global `ForceOocAlgorithm()` flag is set. If either condition is true, constructs `OocAlgo(args...)` and calls `operator()()`. Otherwise, constructs `InCoreAlgo(args...)` and calls `operator()()`.
8383
8484
**Requirements for algorithm classes:**
8585
@@ -166,7 +166,48 @@ Filters/Algorithms/
166166
167167
5. **Register new files**: Add the new algorithm names to the plugin's `AlgorithmList` in `CMakeLists.txt`.
168168
169-
6. **Test both paths**: Run the filter's tests with both in-core and OOC configurations. Both must produce identical results.
169+
6. **Test both paths**: Use `ForceOocAlgorithmGuard` with Catch2 `GENERATE` to exercise both algorithm paths in every build configuration (see below).
170+
171+
## Testing Both Algorithm Paths
172+
173+
CI typically only builds the in-core configuration, so the OOC algorithm path would never be exercised without an explicit override. The `ForceOocAlgorithm()` flag and `ForceOocAlgorithmGuard` RAII class solve this.
174+
175+
### ForceOocAlgorithm
176+
177+
```cpp
178+
bool& ForceOocAlgorithm();
179+
```
180+
181+
Returns a reference to a static bool. When set to `true`, `DispatchAlgorithm` always selects the OOC algorithm class regardless of whether the data arrays use chunked storage. Defaults to `false`.
182+
183+
### ForceOocAlgorithmGuard
184+
185+
```cpp
186+
class ForceOocAlgorithmGuard
187+
{
188+
public:
189+
ForceOocAlgorithmGuard(bool force);
190+
~ForceOocAlgorithmGuard();
191+
// non-copyable, non-movable
192+
};
193+
```
194+
195+
RAII guard that sets `ForceOocAlgorithm()` on construction and restores the previous value on destruction. Use with Catch2 `GENERATE` to run each test case twice — once with the in-core algorithm and once with the OOC algorithm:
196+
197+
```cpp
198+
#include "simplnx/Utilities/AlgorithmDispatch.hpp"
199+
200+
TEST_CASE("MyFilter", "[Plugin][MyFilter]")
201+
{
202+
UnitTest::LoadPlugins();
203+
bool forceOocAlgo = GENERATE(false, true);
204+
const nx::core::ForceOocAlgorithmGuard guard(forceOocAlgo);
205+
206+
// ... test body runs identically for both algorithm paths ...
207+
}
208+
```
209+
210+
This ensures both code paths are tested in every build configuration (in-core and OOC). The OOC algorithm class works correctly on in-core `DataStore` data because the chunk API methods are no-ops for `DataStore` (1 chunk spanning the full volume).
170211

171212
## Performance Expectations
172213

src/Plugins/SimplnxCore/test/IdentifySampleTest.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "simplnx/DataStructure/IDataArray.hpp"
88
#include "simplnx/Parameters/ChoicesParameter.hpp"
99
#include "simplnx/UnitTest/UnitTestCommon.hpp"
10+
#include "simplnx/Utilities/AlgorithmDispatch.hpp"
1011

1112
#include <catch2/catch.hpp>
1213

@@ -22,6 +23,8 @@ const DataPath k_ExemplarArrayPath = Constants::k_DataContainerPath.createChildP
2223
TEST_CASE("SimplnxCore::IdentifySampleFilter", "[SimplnxCore][IdentifySampleFilter]")
2324
{
2425
UnitTest::LoadPlugins();
26+
bool forceOocAlgo = GENERATE(false, true);
27+
const nx::core::ForceOocAlgorithmGuard guard(forceOocAlgo);
2528
const UnitTest::PreferencesSentinel prefsSentinel("Zarr", 100, true);
2629

2730
const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_TestFilesDir, "identify_sample.tar.gz", "identify_sample", true, true);
@@ -82,6 +85,8 @@ TEST_CASE("SimplnxCore::IdentifySampleFilter", "[SimplnxCore][IdentifySampleFilt
8285
TEST_CASE("SimplnxCore::IdentifySampleFilter: Benchmark 200x200x200", "[SimplnxCore][IdentifySampleFilter][Benchmark]")
8386
{
8487
UnitTest::LoadPlugins();
88+
bool forceOocAlgo = GENERATE(false, true);
89+
const nx::core::ForceOocAlgorithmGuard guard(forceOocAlgo);
8590
// 200*200 * 1 byte = 40000 bytes per Z-slice for uint8 mask
8691
const UnitTest::PreferencesSentinel prefsSentinel("Zarr", 40000, true);
8792

src/simplnx/Utilities/AlgorithmDispatch.hpp

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,71 @@ inline bool AnyOutOfCore(std::initializer_list<const IDataArray*> arrays)
4444
return false;
4545
}
4646

47+
/**
48+
* @brief Returns a reference to the global flag that forces DispatchAlgorithm
49+
* to always select the out-of-core algorithm, regardless of storage type.
50+
*
51+
* This is primarily used in unit tests to exercise the OOC algorithm path
52+
* even when data is stored in-core. Use ForceOocAlgorithmGuard for RAII-safe
53+
* toggling in tests.
54+
*
55+
* @return Reference to the static force flag
56+
*/
57+
inline bool& ForceOocAlgorithm()
58+
{
59+
static bool s_force = false;
60+
return s_force;
61+
}
62+
63+
/**
64+
* @brief RAII guard that sets ForceOocAlgorithm() on construction and
65+
* restores the previous value on destruction.
66+
*
67+
* Usage in tests with Catch2 GENERATE:
68+
* @code
69+
* bool forceOoc = GENERATE(false, true);
70+
* const nx::core::ForceOocAlgorithmGuard guard(forceOoc);
71+
* // ... test body runs with both algorithm paths ...
72+
* @endcode
73+
*/
74+
class ForceOocAlgorithmGuard
75+
{
76+
public:
77+
ForceOocAlgorithmGuard(bool force)
78+
: m_Original(ForceOocAlgorithm())
79+
{
80+
ForceOocAlgorithm() = force;
81+
}
82+
83+
~ForceOocAlgorithmGuard()
84+
{
85+
ForceOocAlgorithm() = m_Original;
86+
}
87+
88+
ForceOocAlgorithmGuard(const ForceOocAlgorithmGuard&) = delete;
89+
ForceOocAlgorithmGuard(ForceOocAlgorithmGuard&&) = delete;
90+
ForceOocAlgorithmGuard& operator=(const ForceOocAlgorithmGuard&) = delete;
91+
ForceOocAlgorithmGuard& operator=(ForceOocAlgorithmGuard&&) = delete;
92+
93+
private:
94+
bool m_Original;
95+
};
96+
4797
/**
4898
* @brief Dispatches between two algorithm classes based on whether any of the
49-
* given data arrays use out-of-core (chunked) storage.
99+
* given data arrays use out-of-core (chunked) storage, or if the global
100+
* ForceOocAlgorithm() flag is set.
50101
*
51102
* Some algorithms that perform well on in-memory data (e.g. BFS flood fill with
52103
* random access) become extremely slow when data is stored in disk-backed chunks,
53104
* because each random access may trigger a chunk load/evict cycle. In these cases,
54105
* a different algorithm (e.g. scanline CCL with sequential chunk access) can be
55106
* orders of magnitude faster for OOC data.
56107
*
57-
* This function checks the storage type of the given arrays and instantiates the
58-
* appropriate algorithm class. If *any* array is out-of-core, the OOC algorithm
59-
* is selected. Callers should pass all input and output arrays the filter operates
60-
* on. Both algorithm classes must:
108+
* This function checks the storage type of the given arrays and the global force
109+
* flag. If *any* array is out-of-core or ForceOocAlgorithm() is true, the OOC
110+
* algorithm is selected. Callers should pass all input and output arrays the
111+
* filter operates on. Both algorithm classes must:
61112
* - Be constructible from the same ArgsT... parameter pack
62113
* - Provide operator()() returning Result<>
63114
*
@@ -71,7 +122,7 @@ inline bool AnyOutOfCore(std::initializer_list<const IDataArray*> arrays)
71122
template <typename InCoreAlgo, typename OocAlgo, typename... ArgsT>
72123
Result<> DispatchAlgorithm(std::initializer_list<const IDataArray*> arrays, ArgsT&&... args)
73124
{
74-
if(AnyOutOfCore(arrays))
125+
if(AnyOutOfCore(arrays) || ForceOocAlgorithm())
75126
{
76127
return OocAlgo(std::forward<ArgsT>(args)...)();
77128
}

0 commit comments

Comments
 (0)