diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /xpcom/tests/gtest/TestMultiplexInputStream.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/tests/gtest/TestMultiplexInputStream.cpp')
-rw-r--r-- | xpcom/tests/gtest/TestMultiplexInputStream.cpp | 813 |
1 files changed, 813 insertions, 0 deletions
diff --git a/xpcom/tests/gtest/TestMultiplexInputStream.cpp b/xpcom/tests/gtest/TestMultiplexInputStream.cpp new file mode 100644 index 0000000000..b607208a8d --- /dev/null +++ b/xpcom/tests/gtest/TestMultiplexInputStream.cpp @@ -0,0 +1,813 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsIAsyncInputStream.h" +#include "nsComponentManagerUtils.h" +#include "nsIInputStream.h" +#include "nsIMultiplexInputStream.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "Helpers.h" + +using mozilla::GetCurrentSerialEventTarget; +using mozilla::SpinEventLoopUntil; + +TEST(MultiplexInputStream, Seek_SET) +{ + nsCString buf1; + nsCString buf2; + nsCString buf3; + buf1.AssignLiteral("Hello world"); + buf2.AssignLiteral("The quick brown fox jumped over the lazy dog"); + buf3.AssignLiteral("Foo bar"); + + nsCOMPtr<nsIInputStream> inputStream1; + nsCOMPtr<nsIInputStream> inputStream2; + nsCOMPtr<nsIInputStream> inputStream3; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = multiplexStream->AppendStream(inputStream3); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + int64_t tell; + uint64_t length; + uint32_t count; + char readBuf[4096]; + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(multiplexStream); + ASSERT_TRUE(seekStream); + + // Seek forward in first input stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 1, + length); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, 1); + + // Check read is correct + rv = stream->Read(readBuf, 3, &count); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)3, count); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 4, + length); + ASSERT_EQ(0, strncmp(readBuf, "ell", count)); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, 4); + + // Seek to start of third input stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, + buf1.Length() + buf2.Length()); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)buf3.Length(), length); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, buf1.Length() + buf2.Length()); + + // Check read is correct + rv = stream->Read(readBuf, 5, &count); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)5, count); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)buf3.Length() - 5, length); + ASSERT_EQ(0, strncmp(readBuf, "Foo b", count)); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, buf1.Length() + buf2.Length() + 5); + + // Seek back to start of second stream (covers bug 1272371) + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, buf1.Length()); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)buf2.Length() + buf3.Length(), length); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, buf1.Length()); + + // Check read is correct + rv = stream->Read(readBuf, 6, &count); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)6, count); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)buf2.Length() - 6 + buf3.Length(), length); + ASSERT_EQ(0, strncmp(readBuf, "The qu", count)); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, buf1.Length() + 6); +} + +TEST(MultiplexInputStream, Seek_CUR) +{ + nsCString buf1; + nsCString buf2; + nsCString buf3; + buf1.AssignLiteral("Hello world"); + buf2.AssignLiteral("The quick brown fox jumped over the lazy dog"); + buf3.AssignLiteral("Foo bar"); + + nsCOMPtr<nsIInputStream> inputStream1; + nsCOMPtr<nsIInputStream> inputStream2; + nsCOMPtr<nsIInputStream> inputStream3; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = multiplexStream->AppendStream(inputStream3); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + int64_t tell; + uint64_t length; + uint32_t count; + char readBuf[4096]; + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(multiplexStream); + ASSERT_TRUE(seekStream); + + // Seek forward in first input stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, 1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 1, + length); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, 1); + + // Check read is correct + rv = stream->Read(readBuf, 3, &count); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)3, count); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 4, + length); + ASSERT_EQ(0, strncmp(readBuf, "ell", count)); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, 4); + + // Let's go to the second stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, 11); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, 15); + rv = stream->Read(readBuf, 3, &count); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(readBuf, "qui", count)); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, 18); + + // Let's go back to the first stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, -9); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, 9); + rv = stream->Read(readBuf, 3, &count); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(readBuf, "ldT", count)); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, 12); +} + +TEST(MultiplexInputStream, Seek_END) +{ + nsCString buf1; + nsCString buf2; + nsCString buf3; + buf1.AssignLiteral("Hello world"); + buf2.AssignLiteral("The quick brown fox jumped over the lazy dog"); + buf3.AssignLiteral("Foo bar"); + + nsCOMPtr<nsIInputStream> inputStream1; + nsCOMPtr<nsIInputStream> inputStream2; + nsCOMPtr<nsIInputStream> inputStream3; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = multiplexStream->AppendStream(inputStream3); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + int64_t tell; + uint64_t length; + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(multiplexStream); + ASSERT_TRUE(seekStream); + + // SEEK_END wants <=0 values + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, 1); + ASSERT_TRUE(NS_FAILED(rv)); + + // Let's go to the end. + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)0, length); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, buf1.Length() + buf2.Length() + buf3.Length()); + + // -1 + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, -1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ((uint64_t)1, length); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, buf1.Length() + buf2.Length() + buf3.Length() - 1); + + // Almost at the beginning + tell = 1; + tell -= buf1.Length(); + tell -= buf2.Length(); + tell -= buf3.Length(); + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + rv = stream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(length, buf1.Length() + buf2.Length() + buf3.Length() - 1); + rv = seekStream->Tell(&tell); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(tell, 1); +} + +static already_AddRefed<nsIInputStream> CreateStreamHelper() { + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + nsCString buf1; + buf1.AssignLiteral("Hello"); + + nsCOMPtr<nsIInputStream> inputStream1 = new testing::AsyncStringStream(buf1); + multiplexStream->AppendStream(inputStream1); + + nsCString buf2; + buf2.AssignLiteral(" "); + + nsCOMPtr<nsIInputStream> inputStream2 = new testing::AsyncStringStream(buf2); + multiplexStream->AppendStream(inputStream2); + + nsCString buf3; + buf3.AssignLiteral("World!"); + + nsCOMPtr<nsIInputStream> inputStream3 = new testing::AsyncStringStream(buf3); + multiplexStream->AppendStream(inputStream3); + + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + return stream.forget(); +} + +// AsyncWait - without EventTarget +TEST(MultiplexInputStream, AsyncWait_withoutEventTarget) +{ + nsCOMPtr<nsIInputStream> is = CreateStreamHelper(); + + nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, 0, 0, nullptr)); + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - with EventTarget +TEST(MultiplexInputStream, AsyncWait_withEventTarget) +{ + nsCOMPtr<nsIInputStream> is = CreateStreamHelper(); + + nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, 0, 0, thread)); + + ASSERT_FALSE(cb->Called()); + + // Eventually it is called. + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil([&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - without EventTarget - closureOnly +TEST(MultiplexInputStream, AsyncWait_withoutEventTarget_closureOnly) +{ + nsCOMPtr<nsIInputStream> is = CreateStreamHelper(); + + nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, + nullptr)); + ASSERT_FALSE(cb->Called()); + + ais->CloseWithStatus(NS_ERROR_FAILURE); + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - with EventTarget - closureOnly +TEST(MultiplexInputStream, AsyncWait_withEventTarget_closureOnly) +{ + nsCOMPtr<nsIInputStream> is = CreateStreamHelper(); + + nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback(); + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, + thread)); + + ASSERT_FALSE(cb->Called()); + ais->CloseWithStatus(NS_ERROR_FAILURE); + ASSERT_FALSE(cb->Called()); + + // Eventually it is called. + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil([&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +class ClosedStream final : public nsIInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + ClosedStream() = default; + + NS_IMETHOD + Available(uint64_t* aLength) override { return NS_BASE_STREAM_CLOSED; } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + MOZ_CRASH("This should not be called!"); + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { return NS_OK; } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = true; + return NS_OK; + } + + private: + ~ClosedStream() = default; +}; + +NS_IMPL_ISUPPORTS(ClosedStream, nsIInputStream) + +class AsyncStream final : public nsIAsyncInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit AsyncStream(int64_t aSize) : mState(eBlocked), mSize(aSize) {} + + void Unblock() { mState = eUnblocked; } + + NS_IMETHOD + Available(uint64_t* aLength) override { + *aLength = mState == eBlocked ? 0 : mSize; + return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_OK; + } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + MOZ_CRASH("This should not be called!"); + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { + mState = eClosed; + return NS_OK; + } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = true; + return NS_OK; + } + + NS_IMETHOD + AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override { + MOZ_CRASH("This should not be called!"); + return NS_OK; + } + + NS_IMETHOD + CloseWithStatus(nsresult aStatus) override { return NS_OK; } + + private: + ~AsyncStream() = default; + + enum { eBlocked, eUnblocked, eClosed } mState; + + uint64_t mSize; +}; + +NS_IMPL_ISUPPORTS(AsyncStream, nsIInputStream, nsIAsyncInputStream) + +class BlockingStream final : public nsIInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + BlockingStream() = default; + + NS_IMETHOD + Available(uint64_t* aLength) override { return NS_BASE_STREAM_CLOSED; } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + // We are actually empty. + *aReadCount = 0; + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { return NS_OK; } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = false; + return NS_OK; + } + + private: + ~BlockingStream() = default; +}; + +NS_IMPL_ISUPPORTS(BlockingStream, nsIInputStream) + +TEST(MultiplexInputStream, Available) +{ + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + nsCOMPtr<nsIInputStream> s = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!s); + + nsCOMPtr<nsIAsyncInputStream> as = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!as); + + uint64_t length; + + // The stream returns NS_BASE_STREAM_CLOSED if there are no substreams. + nsresult rv = s->Available(&length); + ASSERT_EQ(NS_BASE_STREAM_CLOSED, rv); + + rv = multiplexStream->AppendStream(new ClosedStream()); + ASSERT_EQ(NS_OK, rv); + + uint64_t asyncSize = 2; + RefPtr<AsyncStream> asyncStream = new AsyncStream(2); + rv = multiplexStream->AppendStream(asyncStream); + ASSERT_EQ(NS_OK, rv); + + nsCString buffer; + buffer.Assign("World!!!"); + + nsCOMPtr<nsIInputStream> stringStream; + rv = NS_NewCStringInputStream(getter_AddRefs(stringStream), buffer); + ASSERT_EQ(NS_OK, rv); + + rv = multiplexStream->AppendStream(stringStream); + ASSERT_EQ(NS_OK, rv); + + // Now we are async. + as = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!as); + + // Available should skip the closed stream and return 0 because the + // asyncStream returns 0 and it's async. + rv = s->Available(&length); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ((uint64_t)0, length); + + asyncStream->Unblock(); + + // Now we should return only the size of the async stream because we don't + // know when this is completed. + rv = s->Available(&length); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(asyncSize, length); + + asyncStream->Close(); + + rv = s->Available(&length); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(buffer.Length(), length); +} + +class NonBufferableStringStream final : public nsIInputStream { + nsCOMPtr<nsIInputStream> mStream; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit NonBufferableStringStream(const nsACString& aBuffer) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + NS_IMETHOD + Available(uint64_t* aLength) override { return mStream->Available(aLength); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + return mStream->IsNonBlocking(aNonBlocking); + } + + private: + ~NonBufferableStringStream() = default; +}; + +NS_IMPL_ISUPPORTS(NonBufferableStringStream, nsIInputStream) + +TEST(MultiplexInputStream, Bufferable) +{ + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + nsCOMPtr<nsIInputStream> s = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!s); + + nsCString buf1; + buf1.AssignLiteral("Hello "); + nsCOMPtr<nsIInputStream> inputStream1; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCString buf2; + buf2.AssignLiteral("world"); + nsCOMPtr<nsIInputStream> inputStream2 = new NonBufferableStringStream(buf2); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(!!stream); + + char buf3[1024]; + uint32_t size = 0; + rv = stream->ReadSegments(NS_CopySegmentToBuffer, buf3, sizeof(buf3), &size); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + ASSERT_EQ(size, buf1.Length() + buf2.Length()); + ASSERT_TRUE(!strncmp(buf3, "Hello world", size)); +} + +TEST(MultiplexInputStream, QILengthInputStream) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + // nsMultiplexInputStream doesn't expose nsIInputStreamLength if there are + // no nsIInputStreamLength sub streams. + { + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream), buf); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + rv = multiplexStream->AppendStream(inputStream); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!fsis); + + nsCOMPtr<nsIAsyncInputStreamLength> afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!afsis); + } + + // nsMultiplexInputStream exposes nsIInputStreamLength if there is one or + // more nsIInputStreamLength sub streams. + { + RefPtr<testing::LengthInputStream> inputStream = + new testing::LengthInputStream(buf, true, false); + + nsresult rv = multiplexStream->AppendStream(inputStream); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!fsis); + + nsCOMPtr<nsIAsyncInputStreamLength> afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!afsis); + } + + // nsMultiplexInputStream exposes nsIAsyncInputStreamLength if there is one + // or more nsIAsyncInputStreamLength sub streams. + { + RefPtr<testing::LengthInputStream> inputStream = + new testing::LengthInputStream(buf, true, true); + + nsresult rv = multiplexStream->AppendStream(inputStream); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!fsis); + + nsCOMPtr<nsIAsyncInputStreamLength> afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!afsis); + } +} + +TEST(MultiplexInputStream, LengthInputStream) +{ + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + // First stream is a a simple one. + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream), buf); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + rv = multiplexStream->AppendStream(inputStream); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // A LengthInputStream, non-async. + RefPtr<testing::LengthInputStream> lengthStream = + new testing::LengthInputStream(buf, true, false); + + rv = multiplexStream->AppendStream(lengthStream); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!fsis); + + // Size is the sum of the 2 streams. + int64_t length; + rv = fsis->Length(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(buf.Length() * 2, length); + + // An async LengthInputStream. + RefPtr<testing::LengthInputStream> asyncLengthStream = + new testing::LengthInputStream(buf, true, true, + NS_BASE_STREAM_WOULD_BLOCK); + + rv = multiplexStream->AppendStream(asyncLengthStream); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIAsyncInputStreamLength> afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!afsis); + + // Now it would block. + rv = fsis->Length(&length); + ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv); + + // Let's read the size async. + RefPtr<testing::LengthCallback> callback = new testing::LengthCallback(); + rv = afsis->AsyncLengthWait(callback, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); })); + ASSERT_EQ(buf.Length() * 3, callback->Size()); + + // Now a negative stream + lengthStream = new testing::LengthInputStream(buf, true, false, NS_OK, true); + + rv = multiplexStream->AppendStream(lengthStream); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + rv = fsis->Length(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_EQ(-1, length); + + // Another async LengthInputStream. + asyncLengthStream = new testing::LengthInputStream( + buf, true, true, NS_BASE_STREAM_WOULD_BLOCK); + + rv = multiplexStream->AppendStream(asyncLengthStream); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + afsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!afsis); + + // Let's read the size async. + RefPtr<testing::LengthCallback> callback1 = new testing::LengthCallback(); + rv = afsis->AsyncLengthWait(callback1, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + RefPtr<testing::LengthCallback> callback2 = new testing::LengthCallback(); + rv = afsis->AsyncLengthWait(callback2, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback2->Called(); })); + ASSERT_FALSE(callback1->Called()); + ASSERT_TRUE(callback2->Called()); +} |