/mordor/tests/pipe_stream.cpp

http://github.com/mozy/mordor · C++ · 407 lines · 339 code · 63 blank · 5 comment · 18 complexity · b48b360731b80e45cbe7e2fdb69a966a MD5 · raw file

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