PageRenderTime 59ms CodeModel.GetById 30ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 0ms

/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
  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}