]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
1 | // |
2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) | |
3 | // | |
4 | // 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 | // | |
7 | // Official repository: https://github.com/boostorg/beast | |
8 | // | |
9 | ||
10 | #ifndef BOOST_BEAST_TEST_STREAM_HPP | |
11 | #define BOOST_BEAST_TEST_STREAM_HPP | |
12 | ||
13 | #include <boost/beast/core/detail/config.hpp> | |
14 | #include <boost/beast/core/bind_handler.hpp> | |
15 | #include <boost/beast/core/flat_buffer.hpp> | |
16 | #include <boost/beast/core/role.hpp> | |
17 | #include <boost/beast/core/string.hpp> | |
18 | #include <boost/beast/_experimental/test/fail_count.hpp> | |
1e59de90 | 19 | #include <boost/beast/_experimental/test/detail/stream_state.hpp> |
92f5a8d4 TL |
20 | #include <boost/asio/async_result.hpp> |
21 | #include <boost/asio/buffer.hpp> | |
22 | #include <boost/asio/error.hpp> | |
23 | #include <boost/asio/executor_work_guard.hpp> | |
1e59de90 | 24 | #include <boost/asio/any_io_executor.hpp> |
92f5a8d4 TL |
25 | #include <boost/asio/io_context.hpp> |
26 | #include <boost/asio/post.hpp> | |
27 | #include <boost/assert.hpp> | |
28 | #include <boost/shared_ptr.hpp> | |
29 | #include <boost/weak_ptr.hpp> | |
30 | #include <boost/throw_exception.hpp> | |
31 | #include <condition_variable> | |
32 | #include <limits> | |
33 | #include <memory> | |
34 | #include <mutex> | |
35 | #include <utility> | |
36 | ||
37 | #if ! BOOST_BEAST_DOXYGEN | |
38 | namespace boost { | |
39 | namespace asio { | |
40 | namespace ssl { | |
41 | template<typename> class stream; | |
42 | } // ssl | |
43 | } // asio | |
44 | } // boost | |
45 | #endif | |
46 | ||
47 | namespace boost { | |
48 | namespace beast { | |
49 | namespace test { | |
50 | ||
51 | /** A two-way socket useful for unit testing | |
52 | ||
53 | An instance of this class simulates a traditional socket, | |
54 | while also providing features useful for unit testing. | |
55 | Each endpoint maintains an independent buffer called | |
56 | the input area. Writes from one endpoint append data | |
57 | to the peer's pending input area. When an endpoint performs | |
58 | a read and data is present in the input area, the data is | |
59 | delivered to the blocking or asynchronous operation. Otherwise | |
60 | the operation is blocked or deferred until data is made | |
61 | available, or until the endpoints become disconnected. | |
62 | ||
63 | These streams may be used anywhere an algorithm accepts a | |
64 | reference to a synchronous or asynchronous read or write | |
65 | stream. It is possible to use a test stream in a call to | |
66 | `net::read_until`, or in a call to | |
67 | @ref boost::beast::http::async_write for example. | |
68 | ||
69 | As with Boost.Asio I/O objects, a @ref stream constructs | |
70 | with a reference to the `net::io_context` to use for | |
71 | handling asynchronous I/O. For asynchronous operations, the | |
72 | stream follows the same rules as a traditional asio socket | |
73 | with respect to how completion handlers for asynchronous | |
74 | operations are performed. | |
75 | ||
76 | To facilitate testing, these streams support some additional | |
77 | features: | |
78 | ||
79 | @li The input area, represented by a @ref beast::basic_flat_buffer, | |
80 | may be directly accessed by the caller to inspect the contents | |
81 | before or after the remote endpoint writes data. This allows | |
82 | a unit test to verify that the received data matches. | |
83 | ||
84 | @li Data may be manually appended to the input area. This data | |
85 | will delivered in the next call to | |
86 | @ref stream::read_some or @ref stream::async_read_some. | |
87 | This allows predefined test vectors to be set up for testing | |
88 | read algorithms. | |
89 | ||
90 | @li The stream may be constructed with a fail count. The | |
91 | stream will eventually fail with a predefined error after a | |
92 | certain number of operations, where the number of operations | |
93 | is controlled by the test. When a test loops over a range of | |
94 | operation counts, it is possible to exercise every possible | |
95 | point of failure in the algorithm being tested. When used | |
96 | correctly the technique allows the tests to reach a high | |
97 | percentage of code coverage. | |
98 | ||
99 | @par Thread Safety | |
100 | @e Distinct @e objects: Safe.@n | |
101 | @e Shared @e objects: Unsafe. | |
102 | The application must also ensure that all asynchronous | |
103 | operations are performed within the same implicit or explicit strand. | |
104 | ||
105 | @par Concepts | |
106 | @li <em>SyncReadStream</em> | |
107 | @li <em>SyncWriteStream</em> | |
108 | @li <em>AsyncReadStream</em> | |
109 | @li <em>AsyncWriteStream</em> | |
110 | */ | |
1e59de90 TL |
111 | template<class Executor = net::any_io_executor> |
112 | class basic_stream; | |
92f5a8d4 | 113 | |
1e59de90 TL |
114 | template<class Executor> |
115 | void | |
116 | teardown( | |
117 | role_type, | |
118 | basic_stream<Executor>& s, | |
119 | boost::system::error_code& ec); | |
92f5a8d4 | 120 | |
1e59de90 TL |
121 | template<class Executor, class TeardownHandler> |
122 | void | |
123 | async_teardown( | |
124 | role_type role, | |
125 | basic_stream<Executor>& s, | |
126 | TeardownHandler&& handler); | |
92f5a8d4 | 127 | |
1e59de90 TL |
128 | template<class Executor> |
129 | class basic_stream | |
130 | { | |
131 | public: | |
132 | /// The type of the executor associated with the object. | |
133 | using executor_type = | |
134 | Executor; | |
92f5a8d4 | 135 | |
1e59de90 TL |
136 | /// Rebinds the socket type to another executor. |
137 | template <typename Executor1> | |
138 | struct rebind_executor | |
92f5a8d4 | 139 | { |
1e59de90 TL |
140 | /// The socket type when rebound to the specified executor. |
141 | typedef basic_stream<Executor1> other; | |
92f5a8d4 TL |
142 | }; |
143 | ||
1e59de90 TL |
144 | private: |
145 | template<class Executor2> | |
146 | friend class basic_stream; | |
147 | ||
148 | boost::shared_ptr<detail::stream_state> in_; | |
149 | boost::weak_ptr<detail::stream_state> out_; | |
92f5a8d4 TL |
150 | |
151 | template<class Handler, class Buffers> | |
152 | class read_op; | |
153 | ||
154 | struct run_read_op; | |
155 | struct run_write_op; | |
156 | ||
92f5a8d4 TL |
157 | static |
158 | void | |
159 | initiate_read( | |
1e59de90 TL |
160 | boost::shared_ptr<detail::stream_state> const& in, |
161 | std::unique_ptr<detail::stream_read_op_base>&& op, | |
92f5a8d4 TL |
162 | std::size_t buf_size); |
163 | ||
164 | #if ! BOOST_BEAST_DOXYGEN | |
165 | // boost::asio::ssl::stream needs these | |
166 | // DEPRECATED | |
167 | template<class> | |
168 | friend class boost::asio::ssl::stream; | |
169 | // DEPRECATED | |
1e59de90 | 170 | using lowest_layer_type = basic_stream; |
92f5a8d4 TL |
171 | // DEPRECATED |
172 | lowest_layer_type& | |
173 | lowest_layer() noexcept | |
174 | { | |
175 | return *this; | |
176 | } | |
177 | // DEPRECATED | |
178 | lowest_layer_type const& | |
179 | lowest_layer() const noexcept | |
180 | { | |
181 | return *this; | |
182 | } | |
183 | #endif | |
184 | ||
185 | public: | |
186 | using buffer_type = flat_buffer; | |
187 | ||
188 | /** Destructor | |
189 | ||
190 | If an asynchronous read operation is pending, it will | |
191 | simply be discarded with no notification to the completion | |
192 | handler. | |
193 | ||
194 | If a connection is established while the stream is destroyed, | |
195 | the peer will see the error `net::error::connection_reset` | |
196 | when performing any reads or writes. | |
197 | */ | |
1e59de90 | 198 | ~basic_stream(); |
92f5a8d4 TL |
199 | |
200 | /** Move Constructor | |
201 | ||
202 | Moving the stream while asynchronous operations are pending | |
203 | results in undefined behavior. | |
204 | */ | |
1e59de90 TL |
205 | basic_stream(basic_stream&& other); |
206 | ||
207 | /** Move Constructor | |
208 | ||
209 | Moving the stream while asynchronous operations are pending | |
210 | results in undefined behavior. | |
211 | */ | |
212 | template<class Executor2> | |
213 | basic_stream(basic_stream<Executor2>&& other) | |
214 | : in_(std::move(other.in_)) | |
215 | , out_(std::move(other.out_)) | |
216 | { | |
217 | BOOST_ASSERT(in_->exec.template target<Executor2>() != nullptr); | |
218 | in_->exec = executor_type(*in_->exec.template target<Executor2>()); | |
219 | } | |
92f5a8d4 TL |
220 | |
221 | /** Move Assignment | |
222 | ||
223 | Moving the stream while asynchronous operations are pending | |
224 | results in undefined behavior. | |
225 | */ | |
1e59de90 TL |
226 | basic_stream& |
227 | operator=(basic_stream&& other); | |
228 | ||
229 | template<class Executor2> | |
230 | basic_stream& | |
231 | operator==(basic_stream<Executor2>&& other); | |
92f5a8d4 TL |
232 | |
233 | /** Construct a stream | |
234 | ||
235 | The stream will be created in a disconnected state. | |
236 | ||
237 | @param ioc The `io_context` object that the stream will use to | |
238 | dispatch handlers for any asynchronous operations. | |
239 | */ | |
1e59de90 TL |
240 | template <typename ExecutionContext> |
241 | explicit basic_stream(ExecutionContext& context, | |
242 | typename std::enable_if< | |
243 | std::is_convertible<ExecutionContext&, net::execution_context&>::value | |
244 | >::type* = 0) | |
245 | : basic_stream(context.get_executor()) | |
246 | { | |
247 | } | |
248 | ||
249 | /** Construct a stream | |
250 | ||
251 | The stream will be created in a disconnected state. | |
252 | ||
253 | @param exec The `executor` object that the stream will use to | |
254 | dispatch handlers for any asynchronous operations. | |
255 | */ | |
92f5a8d4 | 256 | explicit |
1e59de90 | 257 | basic_stream(executor_type exec); |
92f5a8d4 TL |
258 | |
259 | /** Construct a stream | |
260 | ||
261 | The stream will be created in a disconnected state. | |
262 | ||
263 | @param ioc The `io_context` object that the stream will use to | |
264 | dispatch handlers for any asynchronous operations. | |
265 | ||
266 | @param fc The @ref fail_count to associate with the stream. | |
267 | Each I/O operation performed on the stream will increment the | |
268 | fail count. When the fail count reaches its internal limit, | |
269 | a simulated failure error will be raised. | |
270 | */ | |
1e59de90 | 271 | basic_stream( |
92f5a8d4 TL |
272 | net::io_context& ioc, |
273 | fail_count& fc); | |
274 | ||
275 | /** Construct a stream | |
276 | ||
277 | The stream will be created in a disconnected state. | |
278 | ||
279 | @param ioc The `io_context` object that the stream will use to | |
280 | dispatch handlers for any asynchronous operations. | |
281 | ||
282 | @param s A string which will be appended to the input area, not | |
283 | including the null terminator. | |
284 | */ | |
1e59de90 | 285 | basic_stream( |
92f5a8d4 TL |
286 | net::io_context& ioc, |
287 | string_view s); | |
288 | ||
289 | /** Construct a stream | |
290 | ||
291 | The stream will be created in a disconnected state. | |
292 | ||
293 | @param ioc The `io_context` object that the stream will use to | |
294 | dispatch handlers for any asynchronous operations. | |
295 | ||
296 | @param fc The @ref fail_count to associate with the stream. | |
297 | Each I/O operation performed on the stream will increment the | |
298 | fail count. When the fail count reaches its internal limit, | |
299 | a simulated failure error will be raised. | |
300 | ||
301 | @param s A string which will be appended to the input area, not | |
302 | including the null terminator. | |
303 | */ | |
1e59de90 | 304 | basic_stream( |
92f5a8d4 TL |
305 | net::io_context& ioc, |
306 | fail_count& fc, | |
307 | string_view s); | |
308 | ||
309 | /// Establish a connection | |
92f5a8d4 | 310 | void |
1e59de90 | 311 | connect(basic_stream& remote); |
92f5a8d4 TL |
312 | |
313 | /// Return the executor associated with the object. | |
314 | executor_type | |
1e59de90 | 315 | get_executor() noexcept; |
92f5a8d4 TL |
316 | |
317 | /// Set the maximum number of bytes returned by read_some | |
318 | void | |
319 | read_size(std::size_t n) noexcept | |
320 | { | |
321 | in_->read_max = n; | |
322 | } | |
323 | ||
324 | /// Set the maximum number of bytes returned by write_some | |
325 | void | |
326 | write_size(std::size_t n) noexcept | |
327 | { | |
328 | in_->write_max = n; | |
329 | } | |
330 | ||
331 | /// Direct input buffer access | |
332 | buffer_type& | |
333 | buffer() noexcept | |
334 | { | |
335 | return in_->b; | |
336 | } | |
337 | ||
338 | /// Returns a string view representing the pending input data | |
92f5a8d4 TL |
339 | string_view |
340 | str() const; | |
341 | ||
342 | /// Appends a string to the pending input data | |
92f5a8d4 TL |
343 | void |
344 | append(string_view s); | |
345 | ||
346 | /// Clear the pending input area | |
92f5a8d4 TL |
347 | void |
348 | clear(); | |
349 | ||
350 | /// Return the number of reads | |
351 | std::size_t | |
352 | nread() const noexcept | |
353 | { | |
354 | return in_->nread; | |
355 | } | |
356 | ||
357 | /// Return the number of bytes read | |
358 | std::size_t | |
359 | nread_bytes() const noexcept | |
360 | { | |
361 | return in_->nread_bytes; | |
362 | } | |
363 | ||
364 | /// Return the number of writes | |
365 | std::size_t | |
366 | nwrite() const noexcept | |
367 | { | |
368 | return in_->nwrite; | |
369 | } | |
370 | ||
371 | /// Return the number of bytes written | |
372 | std::size_t | |
373 | nwrite_bytes() const noexcept | |
374 | { | |
375 | return in_->nwrite_bytes; | |
376 | } | |
377 | ||
378 | /** Close the stream. | |
379 | ||
380 | The other end of the connection will see | |
381 | `error::eof` after reading all the remaining data. | |
382 | */ | |
92f5a8d4 TL |
383 | void |
384 | close(); | |
385 | ||
386 | /** Close the other end of the stream. | |
387 | ||
388 | This end of the connection will see | |
389 | `error::eof` after reading all the remaining data. | |
390 | */ | |
92f5a8d4 TL |
391 | void |
392 | close_remote(); | |
393 | ||
394 | /** Read some data from the stream. | |
395 | ||
396 | This function is used to read data from the stream. The function call will | |
397 | block until one or more bytes of data has been read successfully, or until | |
398 | an error occurs. | |
399 | ||
400 | @param buffers The buffers into which the data will be read. | |
401 | ||
402 | @returns The number of bytes read. | |
403 | ||
404 | @throws boost::system::system_error Thrown on failure. | |
405 | ||
406 | @note The `read_some` operation may not read all of the requested number of | |
407 | bytes. Consider using the function `net::read` if you need to ensure | |
408 | that the requested amount of data is read before the blocking operation | |
409 | completes. | |
410 | */ | |
411 | template<class MutableBufferSequence> | |
412 | std::size_t | |
413 | read_some(MutableBufferSequence const& buffers); | |
414 | ||
415 | /** Read some data from the stream. | |
416 | ||
417 | This function is used to read data from the stream. The function call will | |
418 | block until one or more bytes of data has been read successfully, or until | |
419 | an error occurs. | |
420 | ||
421 | @param buffers The buffers into which the data will be read. | |
422 | ||
423 | @param ec Set to indicate what error occurred, if any. | |
424 | ||
425 | @returns The number of bytes read. | |
426 | ||
427 | @note The `read_some` operation may not read all of the requested number of | |
428 | bytes. Consider using the function `net::read` if you need to ensure | |
429 | that the requested amount of data is read before the blocking operation | |
430 | completes. | |
431 | */ | |
432 | template<class MutableBufferSequence> | |
433 | std::size_t | |
434 | read_some(MutableBufferSequence const& buffers, | |
435 | error_code& ec); | |
436 | ||
437 | /** Start an asynchronous read. | |
438 | ||
439 | This function is used to asynchronously read one or more bytes of data from | |
440 | the stream. The function call always returns immediately. | |
441 | ||
442 | @param buffers The buffers into which the data will be read. Although the | |
443 | buffers object may be copied as necessary, ownership of the underlying | |
444 | buffers is retained by the caller, which must guarantee that they remain | |
445 | valid until the handler is called. | |
446 | ||
447 | @param handler The completion handler to invoke when the operation | |
448 | completes. The implementation takes ownership of the handler by | |
449 | performing a decay-copy. The equivalent function signature of | |
450 | the handler must be: | |
451 | @code | |
452 | void handler( | |
453 | error_code const& ec, // Result of operation. | |
454 | std::size_t bytes_transferred // Number of bytes read. | |
455 | ); | |
456 | @endcode | |
457 | Regardless of whether the asynchronous operation completes | |
458 | immediately or not, the handler will not be invoked from within | |
459 | this function. Invocation of the handler will be performed in a | |
460 | manner equivalent to using `net::post`. | |
461 | ||
462 | @note The `async_read_some` operation may not read all of the requested number of | |
463 | bytes. Consider using the function `net::async_read` if you need | |
464 | to ensure that the requested amount of data is read before the asynchronous | |
465 | operation completes. | |
466 | */ | |
467 | template< | |
468 | class MutableBufferSequence, | |
1e59de90 TL |
469 | BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::size_t)) ReadHandler |
470 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> | |
471 | BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void(error_code, std::size_t)) | |
92f5a8d4 TL |
472 | async_read_some( |
473 | MutableBufferSequence const& buffers, | |
1e59de90 | 474 | ReadHandler&& handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)); |
92f5a8d4 TL |
475 | |
476 | /** Write some data to the stream. | |
477 | ||
478 | This function is used to write data on the stream. The function call will | |
479 | block until one or more bytes of data has been written successfully, or | |
480 | until an error occurs. | |
481 | ||
482 | @param buffers The data to be written. | |
483 | ||
484 | @returns The number of bytes written. | |
485 | ||
486 | @throws boost::system::system_error Thrown on failure. | |
487 | ||
488 | @note The `write_some` operation may not transmit all of the data to the | |
489 | peer. Consider using the function `net::write` if you need to | |
490 | ensure that all data is written before the blocking operation completes. | |
491 | */ | |
492 | template<class ConstBufferSequence> | |
493 | std::size_t | |
494 | write_some(ConstBufferSequence const& buffers); | |
495 | ||
496 | /** Write some data to the stream. | |
497 | ||
498 | This function is used to write data on the stream. The function call will | |
499 | block until one or more bytes of data has been written successfully, or | |
500 | until an error occurs. | |
501 | ||
502 | @param buffers The data to be written. | |
503 | ||
504 | @param ec Set to indicate what error occurred, if any. | |
505 | ||
506 | @returns The number of bytes written. | |
507 | ||
508 | @note The `write_some` operation may not transmit all of the data to the | |
509 | peer. Consider using the function `net::write` if you need to | |
510 | ensure that all data is written before the blocking operation completes. | |
511 | */ | |
512 | template<class ConstBufferSequence> | |
513 | std::size_t | |
514 | write_some( | |
515 | ConstBufferSequence const& buffers, error_code& ec); | |
516 | ||
517 | /** Start an asynchronous write. | |
518 | ||
519 | This function is used to asynchronously write one or more bytes of data to | |
520 | the stream. The function call always returns immediately. | |
521 | ||
522 | @param buffers The data to be written to the stream. Although the buffers | |
523 | object may be copied as necessary, ownership of the underlying buffers is | |
524 | retained by the caller, which must guarantee that they remain valid until | |
525 | the handler is called. | |
526 | ||
527 | @param handler The completion handler to invoke when the operation | |
528 | completes. The implementation takes ownership of the handler by | |
529 | performing a decay-copy. The equivalent function signature of | |
530 | the handler must be: | |
531 | @code | |
532 | void handler( | |
533 | error_code const& ec, // Result of operation. | |
534 | std::size_t bytes_transferred // Number of bytes written. | |
535 | ); | |
536 | @endcode | |
537 | Regardless of whether the asynchronous operation completes | |
538 | immediately or not, the handler will not be invoked from within | |
539 | this function. Invocation of the handler will be performed in a | |
540 | manner equivalent to using `net::post`. | |
541 | ||
542 | @note The `async_write_some` operation may not transmit all of the data to | |
543 | the peer. Consider using the function `net::async_write` if you need | |
544 | to ensure that all data is written before the asynchronous operation completes. | |
545 | */ | |
546 | template< | |
547 | class ConstBufferSequence, | |
1e59de90 TL |
548 | BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::size_t)) WriteHandler |
549 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> | |
550 | BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void(error_code, std::size_t)) | |
92f5a8d4 TL |
551 | async_write_some( |
552 | ConstBufferSequence const& buffers, | |
1e59de90 TL |
553 | WriteHandler&& handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) |
554 | ); | |
92f5a8d4 TL |
555 | |
556 | #if ! BOOST_BEAST_DOXYGEN | |
557 | friend | |
92f5a8d4 | 558 | void |
1e59de90 | 559 | teardown<>( |
92f5a8d4 | 560 | role_type, |
1e59de90 | 561 | basic_stream& s, |
92f5a8d4 TL |
562 | boost::system::error_code& ec); |
563 | ||
1e59de90 | 564 | template<class Ex2, class TeardownHandler> |
92f5a8d4 | 565 | friend |
92f5a8d4 TL |
566 | void |
567 | async_teardown( | |
568 | role_type role, | |
1e59de90 | 569 | basic_stream<Ex2>& s, |
92f5a8d4 TL |
570 | TeardownHandler&& handler); |
571 | #endif | |
572 | }; | |
573 | ||
574 | #if ! BOOST_BEAST_DOXYGEN | |
1e59de90 | 575 | template<class Executor> |
92f5a8d4 | 576 | void |
1e59de90 | 577 | beast_close_socket(basic_stream<Executor>& s) |
92f5a8d4 TL |
578 | { |
579 | s.close(); | |
580 | } | |
581 | #endif | |
582 | ||
583 | #if BOOST_BEAST_DOXYGEN | |
584 | /** Return a new stream connected to the given stream | |
585 | ||
586 | @param to The stream to connect to. | |
587 | ||
588 | @param args Optional arguments forwarded to the new stream's constructor. | |
589 | ||
590 | @return The new, connected stream. | |
591 | */ | |
1e59de90 | 592 | template<class Executor> |
92f5a8d4 | 593 | template<class... Args> |
1e59de90 TL |
594 | basic_stream |
595 | connect(basic_stream& to, Args&&... args); | |
92f5a8d4 TL |
596 | |
597 | #else | |
1e59de90 TL |
598 | template<class Executor> |
599 | basic_stream<Executor> | |
600 | connect(basic_stream<Executor>& to); | |
92f5a8d4 | 601 | |
1e59de90 | 602 | template<class Executor> |
92f5a8d4 | 603 | void |
1e59de90 | 604 | connect(basic_stream<Executor>& s1, basic_stream<Executor>& s2); |
92f5a8d4 | 605 | |
1e59de90 TL |
606 | template<class Executor, class Arg1, class... ArgN> |
607 | basic_stream<Executor> | |
608 | connect(basic_stream<Executor>& to, Arg1&& arg1, ArgN&&... argn); | |
92f5a8d4 TL |
609 | #endif |
610 | ||
1e59de90 TL |
611 | using stream = basic_stream<>; |
612 | ||
92f5a8d4 TL |
613 | } // test |
614 | } // beast | |
615 | } // boost | |
616 | ||
617 | #include <boost/beast/_experimental/test/impl/stream.hpp> | |
1e59de90 | 618 | //#ifdef BOOST_BEAST_HEADER_ONLY |
92f5a8d4 | 619 | #include <boost/beast/_experimental/test/impl/stream.ipp> |
1e59de90 | 620 | //#endif |
92f5a8d4 TL |
621 | |
622 | #endif |