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