97.35% Lines (110/113) 100.00% Functions (23/23)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
  3 + // Copyright (c) 2026 Michael Vandeberg
3   // 4   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 7   //
7   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
8   // 9   //
9   10  
10   #ifndef BOOST_CAPY_TEST_WRITE_SINK_HPP 11   #ifndef BOOST_CAPY_TEST_WRITE_SINK_HPP
11   #define BOOST_CAPY_TEST_WRITE_SINK_HPP 12   #define BOOST_CAPY_TEST_WRITE_SINK_HPP
12   13  
13   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/buffers.hpp> 15   #include <boost/capy/buffers.hpp>
15   #include <boost/capy/buffers/buffer_copy.hpp> 16   #include <boost/capy/buffers/buffer_copy.hpp>
16   #include <boost/capy/buffers/make_buffer.hpp> 17   #include <boost/capy/buffers/make_buffer.hpp>
17   #include <coroutine> 18   #include <coroutine>
18   #include <boost/capy/ex/io_env.hpp> 19   #include <boost/capy/ex/io_env.hpp>
19   #include <boost/capy/io_result.hpp> 20   #include <boost/capy/io_result.hpp>
20   #include <boost/capy/error.hpp> 21   #include <boost/capy/error.hpp>
21   #include <boost/capy/test/fuse.hpp> 22   #include <boost/capy/test/fuse.hpp>
22   23  
23 - #include <stop_token>  
24   #include <algorithm> 24   #include <algorithm>
25   #include <string> 25   #include <string>
26   #include <string_view> 26   #include <string_view>
27   27  
28   namespace boost { 28   namespace boost {
29   namespace capy { 29   namespace capy {
30   namespace test { 30   namespace test {
31   31  
32   /** A mock sink for testing write operations. 32   /** A mock sink for testing write operations.
33   33  
34   Use this to verify code that performs complete writes without needing 34   Use this to verify code that performs complete writes without needing
35   real I/O. Call @ref write to write data, then @ref data to retrieve 35   real I/O. Call @ref write to write data, then @ref data to retrieve
36   what was written. The associated @ref fuse enables error injection 36   what was written. The associated @ref fuse enables error injection
37   at controlled points. 37   at controlled points.
38   38  
39   This class satisfies the @ref WriteSink concept by providing partial 39   This class satisfies the @ref WriteSink concept by providing partial
40   writes via `write_some` (satisfying @ref WriteStream), complete 40   writes via `write_some` (satisfying @ref WriteStream), complete
41   writes via `write`, and EOF signaling via `write_eof`. 41   writes via `write`, and EOF signaling via `write_eof`.
42   42  
43   @par Thread Safety 43   @par Thread Safety
44   Not thread-safe. 44   Not thread-safe.
45   45  
46   @par Example 46   @par Example
47   @code 47   @code
48   fuse f; 48   fuse f;
49   write_sink ws( f ); 49   write_sink ws( f );
50   50  
51   auto r = f.armed( [&]( fuse& ) -> task<void> { 51   auto r = f.armed( [&]( fuse& ) -> task<void> {
52   auto [ec, n] = co_await ws.write( 52   auto [ec, n] = co_await ws.write(
53   const_buffer( "Hello", 5 ) ); 53   const_buffer( "Hello", 5 ) );
54   if( ec ) 54   if( ec )
55   co_return; 55   co_return;
56   auto [ec2] = co_await ws.write_eof(); 56   auto [ec2] = co_await ws.write_eof();
57   if( ec2 ) 57   if( ec2 )
58   co_return; 58   co_return;
59   // ws.data() returns "Hello" 59   // ws.data() returns "Hello"
60   } ); 60   } );
61   @endcode 61   @endcode
62   62  
63   @see fuse, WriteSink 63   @see fuse, WriteSink
64   */ 64   */
65   class write_sink 65   class write_sink
66   { 66   {
67   fuse f_; 67   fuse f_;
68   std::string data_; 68   std::string data_;
69   std::string expect_; 69   std::string expect_;
70   std::size_t max_write_size_; 70   std::size_t max_write_size_;
71   bool eof_called_ = false; 71   bool eof_called_ = false;
72   72  
73   std::error_code 73   std::error_code
HITCBC 74   236 consume_match_() noexcept 74   236 consume_match_() noexcept
75   { 75   {
HITCBC 76   236 if(data_.empty() || expect_.empty()) 76   236 if(data_.empty() || expect_.empty())
HITCBC 77   228 return {}; 77   228 return {};
HITCBC 78   8 std::size_t const n = (std::min)(data_.size(), expect_.size()); 78   8 std::size_t const n = (std::min)(data_.size(), expect_.size());
HITCBC 79   8 if(std::string_view(data_.data(), n) != 79   8 if(std::string_view(data_.data(), n) !=
HITCBC 80   16 std::string_view(expect_.data(), n)) 80   16 std::string_view(expect_.data(), n))
HITCBC 81   4 return error::test_failure; 81   4 return error::test_failure;
HITCBC 82   4 data_.erase(0, n); 82   4 data_.erase(0, n);
HITCBC 83   4 expect_.erase(0, n); 83   4 expect_.erase(0, n);
HITCBC 84   4 return {}; 84   4 return {};
85   } 85   }
86   86  
87   public: 87   public:
88   /** Construct a write sink. 88   /** Construct a write sink.
89   89  
90   @param f The fuse used to inject errors during writes. 90   @param f The fuse used to inject errors during writes.
91   91  
92   @param max_write_size Maximum bytes transferred per write. 92   @param max_write_size Maximum bytes transferred per write.
93   Use to simulate chunked delivery. 93   Use to simulate chunked delivery.
94   */ 94   */
HITCBC 95   412 explicit write_sink( 95   416 explicit write_sink(
96   fuse f = {}, 96   fuse f = {},
97   std::size_t max_write_size = std::size_t(-1)) noexcept 97   std::size_t max_write_size = std::size_t(-1)) noexcept
HITCBC 98   412 : f_(std::move(f)) 98   416 : f_(std::move(f))
HITCBC 99   412 , max_write_size_(max_write_size) 99   416 , max_write_size_(max_write_size)
100   { 100   {
HITCBC 101   412 } 101   416 }
102   102  
103   /// Return the written data as a string view. 103   /// Return the written data as a string view.
104   std::string_view 104   std::string_view
HITCBC 105   100 data() const noexcept 105   100 data() const noexcept
106   { 106   {
HITCBC 107   100 return data_; 107   100 return data_;
108   } 108   }
109   109  
110   /** Set the expected data for subsequent writes. 110   /** Set the expected data for subsequent writes.
111   111  
112   Stores the expected data and immediately tries to match 112   Stores the expected data and immediately tries to match
113   against any data already written. Matched data is consumed 113   against any data already written. Matched data is consumed
114   from both buffers. 114   from both buffers.
115   115  
116   @param sv The expected data. 116   @param sv The expected data.
117   117  
118   @return An error if existing data does not match. 118   @return An error if existing data does not match.
119   */ 119   */
120   std::error_code 120   std::error_code
HITCBC 121   16 expect(std::string_view sv) 121   16 expect(std::string_view sv)
122   { 122   {
HITCBC 123   16 expect_.assign(sv); 123   16 expect_.assign(sv);
HITCBC 124   16 return consume_match_(); 124   16 return consume_match_();
125   } 125   }
126   126  
127   /// Return the number of bytes written. 127   /// Return the number of bytes written.
128   std::size_t 128   std::size_t
HITCBC 129   6 size() const noexcept 129   9 size() const noexcept
130   { 130   {
HITCBC 131   6 return data_.size(); 131   9 return data_.size();
132   } 132   }
133   133  
134   /// Return whether write_eof has been called. 134   /// Return whether write_eof has been called.
135   bool 135   bool
HITCBC 136   64 eof_called() const noexcept 136   66 eof_called() const noexcept
137   { 137   {
HITCBC 138   64 return eof_called_; 138   66 return eof_called_;
139   } 139   }
140   140  
141   /// Clear all data and reset state. 141   /// Clear all data and reset state.
142   void 142   void
HITCBC 143   4 clear() noexcept 143   4 clear() noexcept
144   { 144   {
HITCBC 145   4 data_.clear(); 145   4 data_.clear();
HITCBC 146   4 expect_.clear(); 146   4 expect_.clear();
HITCBC 147   4 eof_called_ = false; 147   4 eof_called_ = false;
HITCBC 148   4 } 148   4 }
149   149  
150   /** Asynchronously write some data to the sink. 150   /** Asynchronously write some data to the sink.
151   151  
152   Transfers up to `buffer_size( buffers )` bytes from the provided 152   Transfers up to `buffer_size( buffers )` bytes from the provided
153   const buffer sequence to the internal buffer. Before every write, 153   const buffer sequence to the internal buffer. Before every write,
154   the attached @ref fuse is consulted to possibly inject an error. 154   the attached @ref fuse is consulted to possibly inject an error.
155   155  
156   @param buffers The const buffer sequence containing data to write. 156   @param buffers The const buffer sequence containing data to write.
157   157  
158   @return An awaitable that await-returns `(error_code,std::size_t)`. 158   @return An awaitable that await-returns `(error_code,std::size_t)`.
159   159  
  160 + @par Cancellation
  161 + If the environment's stop token has been requested, the write
  162 + completes immediately with `error::canceled` and transfers no
  163 + data. An empty buffer sequence is a no-op that completes
  164 + successfully regardless of the stop token.
  165 +
160   @see fuse 166   @see fuse
161   */ 167   */
162   template<ConstBufferSequence CB> 168   template<ConstBufferSequence CB>
163   auto 169   auto
HITCBC 164   76 write_some(CB buffers) 170   77 write_some(CB buffers)
165   { 171   {
166   struct awaitable 172   struct awaitable
167   { 173   {
168   write_sink* self_; 174   write_sink* self_;
169   CB buffers_; 175   CB buffers_;
  176 + bool canceled_ = false;
170   177  
HITCBC 171 - 76 bool await_ready() const noexcept { return true; } 178 + 77 bool await_ready() const noexcept { return false; }
172   179  
EUB 173 - void await_suspend( 180 + // The operation completes synchronously, but await_suspend is
  181 + // the only place io_env is delivered (the promise's
  182 + // transform_awaiter forwards it here). Returning false means
  183 + // the coroutine does not actually suspend; it resumes
  184 + // immediately, having observed the stop token. See io_env,
  185 + // IoAwaitable.
  186 + bool
HITGNC   187 + 77 await_suspend(
174   std::coroutine_handle<>, 188   std::coroutine_handle<>,
175 - io_env const*) const noexcept 189 + io_env const* env) noexcept
176   { 190   {
HITGNC   191 + 77 canceled_ = env->stop_token.stop_requested();
HITGNC   192 + 77 return false;
EUB 177   } 193   }
178   194  
179   io_result<std::size_t> 195   io_result<std::size_t>
HITCBC 180   76 await_resume() 196   77 await_resume()
181   { 197   {
HITCBC 182   76 if(buffer_empty(buffers_)) 198   77 if(buffer_empty(buffers_))
HITCBC 183   2 return {{}, 0}; 199   2 return {{}, 0};
184   200  
HITGNC   201 + 75 if(canceled_)
HITGNC   202 + 1 return {error::canceled, 0};
  203 +
HITCBC 185   74 auto ec = self_->f_.maybe_fail(); 204   74 auto ec = self_->f_.maybe_fail();
HITCBC 186   53 if(ec) 205   53 if(ec)
HITCBC 187   21 return {ec, 0}; 206   21 return {ec, 0};
188   207  
HITCBC 189   32 std::size_t n = buffer_size(buffers_); 208   32 std::size_t n = buffer_size(buffers_);
HITCBC 190   32 n = (std::min)(n, self_->max_write_size_); 209   32 n = (std::min)(n, self_->max_write_size_);
191   210  
HITCBC 192   32 std::size_t const old_size = self_->data_.size(); 211   32 std::size_t const old_size = self_->data_.size();
HITCBC 193   32 self_->data_.resize(old_size + n); 212   32 self_->data_.resize(old_size + n);
HITCBC 194   32 buffer_copy(make_buffer( 213   32 buffer_copy(make_buffer(
HITCBC 195   32 self_->data_.data() + old_size, n), buffers_, n); 214   32 self_->data_.data() + old_size, n), buffers_, n);
196   215  
HITCBC 197   32 ec = self_->consume_match_(); 216   32 ec = self_->consume_match_();
HITCBC 198   32 if(ec) 217   32 if(ec)
199   { 218   {
MISUBC 200   self_->data_.resize(old_size); 219   self_->data_.resize(old_size);
MISUBC 201   return {ec, 0}; 220   return {ec, 0};
202   } 221   }
203   222  
HITCBC 204   32 return {{}, n}; 223   32 return {{}, n};
205   } 224   }
206   }; 225   };
HITCBC 207   76 return awaitable{this, buffers}; 226   77 return awaitable{this, buffers};
208   } 227   }
209   228  
210   /** Asynchronously write data to the sink. 229   /** Asynchronously write data to the sink.
211   230  
212   Transfers all bytes from the provided const buffer sequence 231   Transfers all bytes from the provided const buffer sequence
213   to the internal buffer. Unlike @ref write_some, this ignores 232   to the internal buffer. Unlike @ref write_some, this ignores
214   `max_write_size` and writes all available data, matching the 233   `max_write_size` and writes all available data, matching the
215   @ref WriteSink semantic contract. 234   @ref WriteSink semantic contract.
216   235  
217   @param buffers The const buffer sequence containing data to write. 236   @param buffers The const buffer sequence containing data to write.
218   237  
219   @return An awaitable that await-returns `(error_code,std::size_t)`. 238   @return An awaitable that await-returns `(error_code,std::size_t)`.
220   239  
  240 + @par Cancellation
  241 + If the environment's stop token has been requested, the write
  242 + completes immediately with `error::canceled` and transfers no
  243 + data.
  244 +
221   @see fuse 245   @see fuse
222   */ 246   */
223   template<ConstBufferSequence CB> 247   template<ConstBufferSequence CB>
224   auto 248   auto
HITCBC 225   302 write(CB buffers) 249   303 write(CB buffers)
226   { 250   {
227   struct awaitable 251   struct awaitable
228   { 252   {
229   write_sink* self_; 253   write_sink* self_;
230   CB buffers_; 254   CB buffers_;
  255 + bool canceled_ = false;
231   256  
HITCBC 232 - 302 bool await_ready() const noexcept { return true; } 257 + 303 bool await_ready() const noexcept { return false; }
233   258  
EUB 234 - void await_suspend( 259 + // Reads the stop token without suspending; see the comment
  260 + // on write_some() for details.
  261 + bool
HITGNC   262 + 303 await_suspend(
235   std::coroutine_handle<>, 263   std::coroutine_handle<>,
236 - io_env const*) const noexcept 264 + io_env const* env) noexcept
237   { 265   {
HITGNC   266 + 303 canceled_ = env->stop_token.stop_requested();
HITGNC   267 + 303 return false;
EUB 238   } 268   }
239   269  
240   io_result<std::size_t> 270   io_result<std::size_t>
HITCBC 241   302 await_resume() 271   303 await_resume()
242   { 272   {
HITGNC   273 + 303 if(canceled_)
HITGNC   274 + 1 return {error::canceled, 0};
  275 +
HITCBC 243   302 auto ec = self_->f_.maybe_fail(); 276   302 auto ec = self_->f_.maybe_fail();
HITCBC 244   241 if(ec) 277   241 if(ec)
HITCBC 245   61 return {ec, 0}; 278   61 return {ec, 0};
246   279  
HITCBC 247   180 std::size_t n = buffer_size(buffers_); 280   180 std::size_t n = buffer_size(buffers_);
HITCBC 248   180 if(n == 0) 281   180 if(n == 0)
HITCBC 249   2 return {{}, 0}; 282   2 return {{}, 0};
250   283  
HITCBC 251   178 std::size_t const old_size = self_->data_.size(); 284   178 std::size_t const old_size = self_->data_.size();
HITCBC 252   178 self_->data_.resize(old_size + n); 285   178 self_->data_.resize(old_size + n);
HITCBC 253   178 buffer_copy(make_buffer( 286   178 buffer_copy(make_buffer(
HITCBC 254   178 self_->data_.data() + old_size, n), buffers_); 287   178 self_->data_.data() + old_size, n), buffers_);
255   288  
HITCBC 256   178 ec = self_->consume_match_(); 289   178 ec = self_->consume_match_();
HITCBC 257   178 if(ec) 290   178 if(ec)
HITCBC 258   2 return {ec, n}; 291   2 return {ec, n};
259   292  
HITCBC 260   176 return {{}, n}; 293   176 return {{}, n};
261   } 294   }
262   }; 295   };
HITCBC 263   302 return awaitable{this, buffers}; 296   303 return awaitable{this, buffers};
264   } 297   }
265   298  
266   /** Atomically write data and signal end-of-stream. 299   /** Atomically write data and signal end-of-stream.
267   300  
268   Transfers all bytes from the provided const buffer sequence to 301   Transfers all bytes from the provided const buffer sequence to
269   the internal buffer and signals end-of-stream. Before the write, 302   the internal buffer and signals end-of-stream. Before the write,
270   the attached @ref fuse is consulted to possibly inject an error 303   the attached @ref fuse is consulted to possibly inject an error
271   for testing fault scenarios. 304   for testing fault scenarios.
272   305  
273   @par Effects 306   @par Effects
274   On success, appends the written bytes to the internal buffer 307   On success, appends the written bytes to the internal buffer
275   and marks the sink as finalized. 308   and marks the sink as finalized.
276   If an error is injected by the fuse, the internal buffer remains 309   If an error is injected by the fuse, the internal buffer remains
277   unchanged. 310   unchanged.
278   311  
279   @par Exception Safety 312   @par Exception Safety
280   No-throw guarantee. 313   No-throw guarantee.
281   314  
  315 + @par Cancellation
  316 + If the environment's stop token has been requested, the operation
  317 + completes immediately with `error::canceled`, transfers no data,
  318 + and does not signal end-of-stream.
  319 +
282   @param buffers The const buffer sequence containing data to write. 320   @param buffers The const buffer sequence containing data to write.
283   321  
284   @return An awaitable that await-returns `(error_code,std::size_t)`. 322   @return An awaitable that await-returns `(error_code,std::size_t)`.
285   323  
286   @see fuse 324   @see fuse
287   */ 325   */
288   template<ConstBufferSequence CB> 326   template<ConstBufferSequence CB>
289   auto 327   auto
HITCBC 290   34 write_eof(CB buffers) 328   35 write_eof(CB buffers)
291   { 329   {
292   struct awaitable 330   struct awaitable
293   { 331   {
294   write_sink* self_; 332   write_sink* self_;
295   CB buffers_; 333   CB buffers_;
  334 + bool canceled_ = false;
296   335  
HITCBC 297 - 34 bool await_ready() const noexcept { return true; } 336 + 35 bool await_ready() const noexcept { return false; }
298   337  
EUB 299 - void await_suspend( 338 + // Reads the stop token without suspending; see the comment
  339 + // on write_some() for details.
  340 + bool
HITGNC   341 + 35 await_suspend(
300   std::coroutine_handle<>, 342   std::coroutine_handle<>,
301 - io_env const*) const noexcept 343 + io_env const* env) noexcept
302   { 344   {
HITGNC   345 + 35 canceled_ = env->stop_token.stop_requested();
HITGNC   346 + 35 return false;
EUB 303   } 347   }
304   348  
305   io_result<std::size_t> 349   io_result<std::size_t>
HITCBC 306   34 await_resume() 350   35 await_resume()
307   { 351   {
HITGNC   352 + 35 if(canceled_)
HITGNC   353 + 1 return {error::canceled, 0};
  354 +
HITCBC 308   34 auto ec = self_->f_.maybe_fail(); 355   34 auto ec = self_->f_.maybe_fail();
HITCBC 309   23 if(ec) 356   23 if(ec)
HITCBC 310   11 return {ec, 0}; 357   11 return {ec, 0};
311   358  
HITCBC 312   12 std::size_t n = buffer_size(buffers_); 359   12 std::size_t n = buffer_size(buffers_);
HITCBC 313   12 if(n > 0) 360   12 if(n > 0)
314   { 361   {
HITCBC 315   10 std::size_t const old_size = self_->data_.size(); 362   10 std::size_t const old_size = self_->data_.size();
HITCBC 316   10 self_->data_.resize(old_size + n); 363   10 self_->data_.resize(old_size + n);
HITCBC 317   10 buffer_copy(make_buffer( 364   10 buffer_copy(make_buffer(
HITCBC 318   10 self_->data_.data() + old_size, n), buffers_); 365   10 self_->data_.data() + old_size, n), buffers_);
319   366  
HITCBC 320   10 ec = self_->consume_match_(); 367   10 ec = self_->consume_match_();
HITCBC 321   10 if(ec) 368   10 if(ec)
MISUBC 322   return {ec, n}; 369   return {ec, n};
323   } 370   }
324   371  
HITCBC 325   12 self_->eof_called_ = true; 372   12 self_->eof_called_ = true;
326   373  
HITCBC 327   12 return {{}, n}; 374   12 return {{}, n};
328   } 375   }
329   }; 376   };
HITCBC 330   34 return awaitable{this, buffers}; 377   35 return awaitable{this, buffers};
331   } 378   }
332   379  
333   /** Signal end-of-stream. 380   /** Signal end-of-stream.
334   381  
335   Marks the sink as finalized, indicating no more data will be 382   Marks the sink as finalized, indicating no more data will be
336   written. Before signaling, the attached @ref fuse is consulted 383   written. Before signaling, the attached @ref fuse is consulted
337   to possibly inject an error for testing fault scenarios. 384   to possibly inject an error for testing fault scenarios.
338   385  
339   @par Effects 386   @par Effects
340   On success, marks the sink as finalized. 387   On success, marks the sink as finalized.
341   If an error is injected by the fuse, the state remains unchanged. 388   If an error is injected by the fuse, the state remains unchanged.
342   389  
343   @par Exception Safety 390   @par Exception Safety
344   No-throw guarantee. 391   No-throw guarantee.
345   392  
  393 + @par Cancellation
  394 + If the environment's stop token has been requested, the operation
  395 + completes immediately with `error::canceled` and does not signal
  396 + end-of-stream.
  397 +
346   @return An awaitable that await-returns `(error_code)`. 398   @return An awaitable that await-returns `(error_code)`.
347   399  
348   @see fuse 400   @see fuse
349   */ 401   */
350   auto 402   auto
HITCBC 351   82 write_eof() 403   83 write_eof()
352   { 404   {
353   struct awaitable 405   struct awaitable
354   { 406   {
355   write_sink* self_; 407   write_sink* self_;
  408 + bool canceled_ = false;
356   409  
HITCBC 357 - 82 bool await_ready() const noexcept { return true; } 410 + 83 bool await_ready() const noexcept { return false; }
358   411  
359 - // This method is required to satisfy Capy's IoAwaitable concept, 412 + // Reads the stop token without suspending; see the comment
360 - // but is never called because await_ready() returns true. 413 + // on write_some() for details.
361 - // See the comment on write(CB buffers) for a detailed explanation. 414 + bool
HITGBC 362 - void await_suspend( 415 + 83 await_suspend(
363   std::coroutine_handle<>, 416   std::coroutine_handle<>,
364 - io_env const*) const noexcept 417 + io_env const* env) noexcept
365   { 418   {
HITGNC   419 + 83 canceled_ = env->stop_token.stop_requested();
HITGNC   420 + 83 return false;
EUB 366   } 421   }
367   422  
368   io_result<> 423   io_result<>
HITCBC 369   82 await_resume() 424   83 await_resume()
370   { 425   {
HITGNC   426 + 83 if(canceled_)
HITGNC   427 + 1 return {error::canceled};
  428 +
HITCBC 371   82 auto ec = self_->f_.maybe_fail(); 429   82 auto ec = self_->f_.maybe_fail();
HITCBC 372   60 if(ec) 430   60 if(ec)
HITCBC 373   22 return {ec}; 431   22 return {ec};
374   432  
HITCBC 375   38 self_->eof_called_ = true; 433   38 self_->eof_called_ = true;
HITCBC 376   38 return {}; 434   38 return {};
377   } 435   }
378   }; 436   };
HITCBC 379   82 return awaitable{this}; 437   83 return awaitable{this};
380   } 438   }
381   }; 439   };
382   440  
383   } // test 441   } // test
384   } // capy 442   } // capy
385   } // boost 443   } // boost
386   444  
387   #endif 445   #endif