/mordor/tests/pipe_stream.cpp
C++ | 407 lines | 339 code | 63 blank | 5 comment | 18 complexity | b48b360731b80e45cbe7e2fdb69a966a MD5 | raw file
1// Copyright (c) 2009 - Mozy, Inc. 2 3#include <boost/bind.hpp> 4 5#include "mordor/exception.h" 6#include "mordor/fiber.h" 7#include "mordor/scheduler.h" 8#include "mordor/streams/buffer.h" 9#include "mordor/streams/stream.h" 10#include "mordor/streams/pipe.h" 11#include "mordor/test/test.h" 12#include "mordor/workerpool.h" 13 14using namespace Mordor; 15using namespace Mordor::Test; 16 17MORDOR_UNITTEST(PipeStream, basic) 18{ 19 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 20 21 MORDOR_TEST_ASSERT(pipe.first->supportsRead()); 22 MORDOR_TEST_ASSERT(pipe.first->supportsWrite()); 23 MORDOR_TEST_ASSERT(!pipe.first->supportsSeek()); 24 MORDOR_TEST_ASSERT(!pipe.first->supportsSize()); 25 MORDOR_TEST_ASSERT(!pipe.first->supportsTruncate()); 26 MORDOR_TEST_ASSERT(!pipe.first->supportsFind()); 27 MORDOR_TEST_ASSERT(!pipe.first->supportsUnread()); 28 MORDOR_TEST_ASSERT(pipe.second->supportsRead()); 29 MORDOR_TEST_ASSERT(pipe.second->supportsWrite()); 30 MORDOR_TEST_ASSERT(!pipe.second->supportsSeek()); 31 MORDOR_TEST_ASSERT(!pipe.second->supportsSize()); 32 MORDOR_TEST_ASSERT(!pipe.second->supportsTruncate()); 33 MORDOR_TEST_ASSERT(!pipe.second->supportsFind()); 34 MORDOR_TEST_ASSERT(!pipe.second->supportsUnread()); 35 36 Buffer read; 37 MORDOR_TEST_ASSERT_EQUAL(pipe.first->write("a"), 1u); 38 MORDOR_TEST_ASSERT_EQUAL(pipe.second->read(read, 10), 1u); 39 MORDOR_TEST_ASSERT(read == "a"); 40 pipe.first->close(); 41 MORDOR_TEST_ASSERT_EQUAL(pipe.second->read(read, 10), 0u); 42} 43 44static void basicInFibers(Stream::ptr stream, int &sequence) 45{ 46 MORDOR_TEST_ASSERT_EQUAL(sequence, 1); 47 MORDOR_TEST_ASSERT_EQUAL(stream->write("a"), 1u); 48 stream->close(); 49 stream->flush(); 50 ++sequence; 51 MORDOR_TEST_ASSERT_EQUAL(sequence, 3); 52} 53 54MORDOR_UNITTEST(PipeStream, basicInFibers) 55{ 56 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 57 // pool must destruct before pipe, because when pool destructs it 58 // waits for the other fiber to complete, which has a weak ref 59 // to pipe.second; if pipe.second is gone, it will throw an 60 // exception that we don't want 61 WorkerPool pool; 62 int sequence = 1; 63 64 pool.schedule(Fiber::ptr(new Fiber(boost::bind(&basicInFibers, pipe.first, boost::ref(sequence))))); 65 66 Buffer read; 67 MORDOR_TEST_ASSERT_EQUAL(pipe.second->read(read, 10), 1u); 68 MORDOR_TEST_ASSERT(read == "a"); 69 ++sequence; 70 MORDOR_TEST_ASSERT_EQUAL(sequence, 2); 71 MORDOR_TEST_ASSERT_EQUAL(pipe.second->read(read, 10), 0u); 72} 73 74MORDOR_UNITTEST(PipeStream, readerClosed1) 75{ 76 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 77 78 pipe.second->close(); 79 MORDOR_TEST_ASSERT_EXCEPTION(pipe.first->write("a"), BrokenPipeException); 80 pipe.first->flush(); 81} 82 83MORDOR_UNITTEST(PipeStream, readerClosed2) 84{ 85 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 86 87 MORDOR_TEST_ASSERT_EQUAL(pipe.first->write("a"), 1u); 88 pipe.second->close(); 89 MORDOR_TEST_ASSERT_EXCEPTION(pipe.first->flush(), BrokenPipeException); 90} 91 92MORDOR_UNITTEST(PipeStream, readerGone) 93{ 94 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 95 96 pipe.second.reset(); 97 MORDOR_TEST_ASSERT_EXCEPTION(pipe.first->write("a"), BrokenPipeException); 98} 99 100MORDOR_UNITTEST(PipeStream, readerGoneFlush) 101{ 102 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 103 104 MORDOR_TEST_ASSERT_EQUAL(pipe.first->write("a"), 1u); 105 pipe.second.reset(); 106 MORDOR_TEST_ASSERT_EXCEPTION(pipe.first->flush(), BrokenPipeException); 107} 108 109MORDOR_UNITTEST(PipeStream, readerGoneReadEverything) 110{ 111 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 112 113 pipe.second.reset(); 114 pipe.first->flush(); 115} 116 117MORDOR_UNITTEST(PipeStream, writerGone) 118{ 119 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 120 121 pipe.first.reset(); 122 Buffer read; 123 MORDOR_TEST_ASSERT_EXCEPTION(pipe.second->read(read, 10), BrokenPipeException); 124} 125 126static void blockingRead(Stream::ptr stream, int &sequence) 127{ 128 MORDOR_TEST_ASSERT_EQUAL(++sequence, 2); 129 MORDOR_TEST_ASSERT_EQUAL(stream->write("hello"), 5u); 130 MORDOR_TEST_ASSERT_EQUAL(++sequence, 3); 131} 132 133MORDOR_UNITTEST(PipeStream, blockingRead) 134{ 135 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(5); 136 WorkerPool pool; 137 int sequence = 1; 138 139 pool.schedule(Fiber::ptr(new Fiber(boost::bind(&blockingRead, pipe.second, 140 boost::ref(sequence))))); 141 142 Buffer output; 143 MORDOR_TEST_ASSERT_EQUAL(pipe.first->read(output, 10), 5u); 144 MORDOR_TEST_ASSERT_EQUAL(++sequence, 4); 145 MORDOR_TEST_ASSERT(output == "hello"); 146} 147 148static void blockingWrite(Stream::ptr stream, int &sequence) 149{ 150 MORDOR_TEST_ASSERT_EQUAL(++sequence, 3); 151 Buffer output; 152 MORDOR_TEST_ASSERT_EQUAL(stream->read(output, 10), 5u); 153 MORDOR_TEST_ASSERT(output == "hello"); 154} 155 156MORDOR_UNITTEST(PipeStream, blockingWrite) 157{ 158 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(5); 159 WorkerPool pool; 160 int sequence = 1; 161 162 pool.schedule(Fiber::ptr(new Fiber(boost::bind(&blockingWrite, pipe.second, 163 boost::ref(sequence))))); 164 165 MORDOR_TEST_ASSERT_EQUAL(pipe.first->write("hello"), 5u); 166 MORDOR_TEST_ASSERT_EQUAL(++sequence, 2); 167 MORDOR_TEST_ASSERT_EQUAL(pipe.first->write("world"), 5u); 168 MORDOR_TEST_ASSERT_EQUAL(++sequence, 4); 169 Buffer output; 170 MORDOR_TEST_ASSERT_EQUAL(pipe.second->read(output, 10), 5u); 171 MORDOR_TEST_ASSERT_EQUAL(++sequence, 5); 172 MORDOR_TEST_ASSERT(output == "world"); 173} 174 175MORDOR_UNITTEST(PipeStream, oversizedWrite) 176{ 177 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(5); 178 179 MORDOR_TEST_ASSERT_EQUAL(pipe.first->write("helloworld"), 5u); 180 Buffer output; 181 MORDOR_TEST_ASSERT_EQUAL(pipe.second->read(output, 10), 5u); 182 MORDOR_TEST_ASSERT(output == "hello"); 183} 184 185static void closeOnBlockingReader(Stream::ptr stream, int &sequence) 186{ 187 MORDOR_TEST_ASSERT_EQUAL(++sequence, 2); 188 stream->close(); 189 MORDOR_TEST_ASSERT_EQUAL(++sequence, 3); 190} 191 192MORDOR_UNITTEST(PipeStream, closeOnBlockingReader) 193{ 194 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 195 WorkerPool pool; 196 int sequence = 1; 197 198 pool.schedule(Fiber::ptr(new Fiber(boost::bind(&closeOnBlockingReader, 199 pipe.first, boost::ref(sequence))))); 200 201 Buffer output; 202 MORDOR_TEST_ASSERT_EQUAL(pipe.second->read(output, 10), 0u); 203 MORDOR_TEST_ASSERT_EQUAL(++sequence, 4); 204} 205 206static void closeOnBlockingWriter(Stream::ptr stream, int &sequence) 207{ 208 MORDOR_TEST_ASSERT_EQUAL(++sequence, 3); 209 stream->close(); 210 MORDOR_TEST_ASSERT_EQUAL(++sequence, 4); 211} 212 213MORDOR_UNITTEST(PipeStream, closeOnBlockingWriter) 214{ 215 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(5); 216 WorkerPool pool; 217 int sequence = 1; 218 219 pool.schedule(Fiber::ptr(new Fiber(boost::bind(&closeOnBlockingWriter, pipe.first, 220 boost::ref(sequence))))); 221 222 MORDOR_TEST_ASSERT_EQUAL(pipe.second->write("hello"), 5u); 223 MORDOR_TEST_ASSERT_EQUAL(++sequence, 2); 224 MORDOR_TEST_ASSERT_EXCEPTION(pipe.second->write("world"), BrokenPipeException); 225 MORDOR_TEST_ASSERT_EQUAL(++sequence, 5); 226} 227 228static void destructOnBlockingReader(boost::weak_ptr<Stream> weakStream, int &sequence) 229{ 230 Stream::ptr stream(weakStream); 231 Fiber::yield(); 232 MORDOR_TEST_ASSERT_EQUAL(++sequence, 2); 233 MORDOR_TEST_ASSERT(stream.unique()); 234 stream.reset(); 235 MORDOR_TEST_ASSERT_EQUAL(++sequence, 3); 236} 237 238MORDOR_UNITTEST(PipeStream, destructOnBlockingReader) 239{ 240 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 241 WorkerPool pool; 242 int sequence = 1; 243 244 Fiber::ptr f = Fiber::ptr(new Fiber(boost::bind(&destructOnBlockingReader, 245 boost::weak_ptr<Stream>(pipe.first), boost::ref(sequence)))); 246 f->call(); 247 pipe.first.reset(); 248 pool.schedule(f); 249 250 Buffer output; 251 MORDOR_TEST_ASSERT_EXCEPTION(pipe.second->read(output, 10), BrokenPipeException); 252 MORDOR_TEST_ASSERT_EQUAL(++sequence, 4); 253} 254 255static void destructOnBlockingWriter(boost::weak_ptr<Stream> weakStream, int &sequence) 256{ 257 Stream::ptr stream(weakStream); 258 Fiber::yield(); 259 MORDOR_TEST_ASSERT_EQUAL(++sequence, 3); 260 MORDOR_TEST_ASSERT(stream.unique()); 261 stream.reset(); 262 MORDOR_TEST_ASSERT_EQUAL(++sequence, 4); 263} 264 265MORDOR_UNITTEST(PipeStream, destructOnBlockingWriter) 266{ 267 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(5); 268 WorkerPool pool; 269 int sequence = 1; 270 271 Fiber::ptr f = Fiber::ptr(new Fiber(boost::bind(&destructOnBlockingWriter, 272 boost::weak_ptr<Stream>(pipe.first), boost::ref(sequence)))); 273 f->call(); 274 pipe.first.reset(); 275 pool.schedule(f); 276 277 MORDOR_TEST_ASSERT_EQUAL(pipe.second->write("hello"), 5u); 278 MORDOR_TEST_ASSERT_EQUAL(++sequence, 2); 279 MORDOR_TEST_ASSERT_EXCEPTION(pipe.second->write("world"), BrokenPipeException); 280 MORDOR_TEST_ASSERT_EQUAL(++sequence, 5); 281} 282 283static void cancelOnBlockingReader(Stream::ptr stream, int &sequence) 284{ 285 MORDOR_TEST_ASSERT_EQUAL(++sequence, 2); 286 stream->cancelRead(); 287 MORDOR_TEST_ASSERT_EQUAL(++sequence, 3); 288} 289 290MORDOR_UNITTEST(PipeStream, cancelOnBlockingReader) 291{ 292 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 293 WorkerPool pool; 294 int sequence = 1; 295 296 pool.schedule(Fiber::ptr(new Fiber(boost::bind(&cancelOnBlockingReader, 297 pipe.first, boost::ref(sequence))))); 298 299 Buffer output; 300 MORDOR_TEST_ASSERT_EXCEPTION(pipe.first->read(output, 10), OperationAbortedException); 301 MORDOR_TEST_ASSERT_EQUAL(++sequence, 4); 302 MORDOR_TEST_ASSERT_EXCEPTION(pipe.first->read(output, 10), OperationAbortedException); 303} 304 305static void cancelOnBlockingWriter(Stream::ptr stream, int &sequence) 306{ 307 MORDOR_TEST_ASSERT_EQUAL(++sequence, 2); 308 char buffer[4096]; 309 memset(buffer, 0, sizeof(buffer)); 310 MORDOR_TEST_ASSERT_EXCEPTION( 311 while (true) stream->write(buffer, 4096), 312 OperationAbortedException); 313 MORDOR_TEST_ASSERT_EQUAL(++sequence, 4); 314 MORDOR_TEST_ASSERT_EXCEPTION(stream->write(buffer, 4096), OperationAbortedException); 315} 316 317MORDOR_UNITTEST(PipeStream, cancelOnBlockingWriter) 318{ 319 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(5); 320 WorkerPool pool; 321 int sequence = 1; 322 323 pool.schedule(Fiber::ptr(new Fiber(boost::bind(&cancelOnBlockingWriter, pipe.first, 324 boost::ref(sequence))))); 325 Scheduler::yield(); 326 MORDOR_TEST_ASSERT_EQUAL(++sequence, 3); 327 pipe.first->cancelWrite(); 328 pool.dispatch(); 329 MORDOR_TEST_ASSERT_EQUAL(++sequence, 5); 330} 331 332static void threadStress(Stream::ptr stream) 333{ 334 size_t totalRead = 0; 335 size_t totalWritten = 0; 336 size_t buf[64]; 337 Buffer buffer; 338 for (int i = 0; i < 10000; ++i) { 339 if (i % 2) { 340 size_t toRead = 64; 341 size_t read = stream->read(buffer, toRead * sizeof(size_t)); 342 MORDOR_TEST_ASSERT(read % sizeof(size_t) == 0); 343 buffer.copyOut(&buf, read); 344 for (size_t j = 0; read > 0; read -= sizeof(size_t), ++j) { 345 MORDOR_TEST_ASSERT_EQUAL(buf[j], ++totalRead); 346 } 347 buffer.clear(); 348 } else { 349 size_t toWrite = 64; 350 for (size_t j = 0; j < toWrite; ++j) { 351 buf[j] = ++totalWritten; 352 } 353 buffer.copyIn(buf, toWrite * sizeof(size_t)); 354 size_t written = stream->write(buffer, toWrite * sizeof(size_t)); 355 totalWritten -= (toWrite - written / sizeof(size_t)); 356 buffer.clear(); 357 } 358 } 359 stream->close(Stream::WRITE); 360 while (true) { 361 size_t toRead = 64; 362 size_t read = stream->read(buffer, toRead); 363 if (read == 0) 364 break; 365 MORDOR_TEST_ASSERT(read % sizeof(size_t) == 0); 366 buffer.copyOut(&buf, read); 367 for (size_t i = 0; read > 0; read -= sizeof(size_t), ++i) { 368 MORDOR_TEST_ASSERT_EQUAL(buf[i], ++totalRead); 369 } 370 buffer.clear(); 371 } 372 stream->flush(); 373} 374 375MORDOR_UNITTEST(PipeStream, threadStress) 376{ 377 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 378 WorkerPool pool(2); 379 380 pool.schedule(Fiber::ptr(new Fiber(boost::bind(&threadStress, pipe.first)))); 381 threadStress(pipe.second); 382} 383 384static void closed(bool &remoteClosed) 385{ 386 remoteClosed = true; 387} 388 389MORDOR_UNITTEST(PipeStream, eventOnRemoteClose) 390{ 391 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 392 393 bool remoteClosed = false; 394 pipe.first->onRemoteClose(boost::bind(&closed, boost::ref(remoteClosed))); 395 pipe.second->close(); 396 MORDOR_TEST_ASSERT(remoteClosed); 397} 398 399MORDOR_UNITTEST(PipeStream, eventOnRemoteReset) 400{ 401 std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream(); 402 403 bool remoteClosed = false; 404 pipe.first->onRemoteClose(boost::bind(&closed, boost::ref(remoteClosed))); 405 pipe.second.reset(); 406 MORDOR_TEST_ASSERT(remoteClosed); 407}