1818#include < stdexec/execution.hpp>
1919#include < test_common/catch2.hpp>
2020
21+ #include < array>
2122#include < cstdint>
2223#include < exception>
24+ #include < limits>
25+ #include < utility>
2326
2427namespace ex = STDEXEC ;
2528
@@ -32,7 +35,7 @@ namespace
3235 [[nodiscard]]
3336 auto available_parallelism () const noexcept -> std::uint32_t
3437 {
35- return 1 ;
38+ return parallelism_ ;
3639 }
3740
3841 [[nodiscard]]
@@ -47,7 +50,8 @@ namespace
4750 task->execute_ (task, tid);
4851 }
4952
50- std::uint32_t enqueued_ = 0 ;
53+ std::uint32_t parallelism_ = 1 ;
54+ std::uint32_t enqueued_ = 0 ;
5155 };
5256
5357 struct value_capture_error
@@ -126,15 +130,137 @@ namespace
126130 CHECK (pool.enqueued_ == 1 );
127131 }
128132
129- TEST_CASE (" thread_pool_base bulk does not invoke the function with a negative shape" ,
133+ TEMPLATE_TEST_CASE (" thread_pool_base bulk handles pool sizes wider than the shape" ,
134+ " [thread_pool_base][bulk]" ,
135+ std::uint8_t ,
136+ std::int8_t ,
137+ std::uint16_t ,
138+ std::int16_t ,
139+ std::int32_t )
140+ {
141+ inline_test_thread_pool pool;
142+ pool.parallelism_ = GENERATE (256u , 257u , 65'536u , 65'537u , 0x80000000u , 0xffffffffu );
143+ completion_state state;
144+ std::array<int , 5 > visits{};
145+ auto const shape = GENERATE (TestType{1 }, TestType{3 }, TestType{5 });
146+
147+ SECTION (" chunked" )
148+ {
149+ auto sndr = ex::schedule (pool.get_scheduler ())
150+ | ex::bulk_chunked (ex::par,
151+ shape,
152+ [&](TestType begin, TestType end) noexcept
153+ {
154+ for (auto i = begin; i < end; ++i)
155+ {
156+ ++visits[i];
157+ }
158+ });
159+ auto op = ex::connect (std::move (sndr), counting_receiver{&state});
160+ ex::start (op);
161+ }
162+
163+ SECTION (" unchunked" )
164+ {
165+ auto sndr = ex::schedule (pool.get_scheduler ())
166+ | ex::bulk_unchunked (ex::par, shape, [&](TestType i) noexcept { ++visits[i]; });
167+ auto op = ex::connect (std::move (sndr), counting_receiver{&state});
168+ ex::start (op);
169+ }
170+
171+ REQUIRE (state.completions_ == 1 );
172+ CHECK_FALSE (state.error_ );
173+ CHECK (pool.enqueued_ == static_cast <std::uint32_t >(shape) + 1 );
174+ for (std::size_t i = 0 ; i < visits.size (); ++i)
175+ {
176+ CHECK (visits[i] == (i < static_cast <std::size_t >(shape) ? 1 : 0 ));
177+ }
178+ }
179+
180+ TEST_CASE (" thread_pool_base bulk covers a maximal uint8 shape" , " [thread_pool_base][bulk]" )
181+ {
182+ inline_test_thread_pool pool;
183+ pool.parallelism_ = 256 ;
184+ completion_state state;
185+ std::array<int , 255 > visits{};
186+
187+ auto sndr = ex::schedule (pool.get_scheduler ())
188+ | ex::bulk_unchunked (ex::par,
189+ std::uint8_t {255 },
190+ [&](std::uint8_t i) noexcept { ++visits[i]; });
191+ auto op = ex::connect (std::move (sndr), counting_receiver{&state});
192+ ex::start (op);
193+
194+ CHECK (state.completions_ == 1 );
195+ CHECK_FALSE (state.error_ );
196+ CHECK (pool.enqueued_ == 256 );
197+ for (int count: visits)
198+ {
199+ CHECK (count == 1 );
200+ }
201+ }
202+
203+ TEST_CASE (" thread_pool_base bulk preserves shapes wider than the worker count" ,
204+ " [thread_pool_base][bulk]" )
205+ {
206+ inline_test_thread_pool pool;
207+ pool.parallelism_ = 2 ;
208+ completion_state state;
209+ auto const shape = GENERATE (std::uint64_t {1 } << 32 ,
210+ (std::numeric_limits<std::uint64_t >::max)());
211+ std::uint64_t visited = 0 ;
212+
213+ auto sndr = ex::schedule (pool.get_scheduler ())
214+ | ex::bulk_chunked (ex::par,
215+ shape,
216+ [&](std::uint64_t begin, std::uint64_t end) noexcept
217+ { visited += end - begin; });
218+ auto op = ex::connect (std::move (sndr), counting_receiver{&state});
219+ ex::start (op);
220+
221+ CHECK (state.completions_ == 1 );
222+ CHECK_FALSE (state.error_ );
223+ CHECK (visited == shape);
224+ CHECK (pool.enqueued_ == 3 );
225+ }
226+
227+ TEST_CASE (" thread_pool_base sequential bulk uses one worker with a narrow shape" ,
228+ " [thread_pool_base][bulk]" )
229+ {
230+ inline_test_thread_pool pool;
231+ pool.parallelism_ = 256 ;
232+ completion_state state;
233+ int visited = 0 ;
234+ int bulk_calls = 0 ;
235+
236+ auto sndr = ex::schedule (pool.get_scheduler ())
237+ | ex::bulk_chunked (ex::seq,
238+ std::uint8_t {3 },
239+ [&](std::uint8_t begin, std::uint8_t end) noexcept
240+ {
241+ visited += end - begin;
242+ ++bulk_calls;
243+ });
244+ auto op = ex::connect (std::move (sndr), counting_receiver{&state});
245+ ex::start (op);
246+
247+ CHECK (state.completions_ == 1 );
248+ CHECK_FALSE (state.error_ );
249+ CHECK (visited == 3 );
250+ CHECK (bulk_calls == 1 );
251+ CHECK (pool.enqueued_ == 2 );
252+ }
253+
254+ TEST_CASE (" thread_pool_base bulk does not invoke the function with a nonpositive shape" ,
130255 " [thread_pool_base][bulk]" )
131256 {
132257 inline_test_thread_pool pool;
133258 completion_state state;
134259 int bulk_calls = 0 ;
135260
136- auto sndr = ex::schedule (pool.get_scheduler ())
137- | ex::bulk_chunked (ex::par, -1 , [&](int , int ) noexcept { ++bulk_calls; });
261+ auto const shape = GENERATE (-1 , 0 );
262+ auto sndr = ex::schedule (pool.get_scheduler ())
263+ | ex::bulk_chunked (ex::par, shape, [&](int , int ) noexcept { ++bulk_calls; });
138264 auto op = ex::connect (std::move (sndr), counting_receiver{&state});
139265
140266 ex::start (op);
0 commit comments