1 #include "gtest/gtest.h"
2 
3 #include "mozilla/NonBlockingAsyncInputStream.h"
4 #include "mozilla/SpinEventLoopUntil.h"
5 #include "nsIAsyncInputStream.h"
6 #include "nsIThread.h"
7 #include "nsStreamUtils.h"
8 #include "nsString.h"
9 #include "nsStringStream.h"
10 #include "Helpers.h"
11 
12 using mozilla::NonBlockingAsyncInputStream;
13 using mozilla::SpinEventLoopUntil;
14 
TEST(TestNonBlockingAsyncInputStream,Simple)15 TEST(TestNonBlockingAsyncInputStream, Simple)
16 {
17   nsCString data;
18   data.Assign("Hello world!");
19 
20   // It should not be async.
21   bool nonBlocking = false;
22   nsCOMPtr<nsIAsyncInputStream> async;
23 
24   {
25     // Let's create a test string inputStream
26     nsCOMPtr<nsIInputStream> stream;
27     ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
28 
29     async = do_QueryInterface(stream);
30     ASSERT_EQ(nullptr, async);
31 
32     // It must be non-blocking
33     ASSERT_EQ(NS_OK, stream->IsNonBlocking(&nonBlocking));
34     ASSERT_TRUE(nonBlocking);
35 
36     // Here the non-blocking stream.
37     ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
38                          stream.forget(), getter_AddRefs(async)));
39   }
40   ASSERT_TRUE(!!async);
41 
42   // Still non-blocking
43   ASSERT_EQ(NS_OK, async->IsNonBlocking(&nonBlocking));
44   ASSERT_TRUE(nonBlocking);
45 
46   // Testing ::Available()
47   uint64_t length;
48   ASSERT_EQ(NS_OK, async->Available(&length));
49   ASSERT_EQ(data.Length(), length);
50 
51   // Read works fine.
52   char buffer[1024];
53   uint32_t read = 0;
54   ASSERT_EQ(NS_OK, async->Read(buffer, sizeof(buffer), &read));
55   ASSERT_EQ(data.Length(), read);
56   ASSERT_TRUE(data.Equals(nsCString(buffer, read)));
57 }
58 
59 class ReadSegmentsData {
60  public:
ReadSegmentsData(nsIInputStream * aStream,char * aBuffer)61   ReadSegmentsData(nsIInputStream* aStream, char* aBuffer)
62       : mStream(aStream), mBuffer(aBuffer) {}
63 
64   nsIInputStream* mStream;
65   char* mBuffer;
66 };
67 
ReadSegmentsFunction(nsIInputStream * aInStr,void * aClosure,const char * aBuffer,uint32_t aOffset,uint32_t aCount,uint32_t * aCountWritten)68 static nsresult ReadSegmentsFunction(nsIInputStream* aInStr, void* aClosure,
69                                      const char* aBuffer, uint32_t aOffset,
70                                      uint32_t aCount, uint32_t* aCountWritten) {
71   ReadSegmentsData* data = static_cast<ReadSegmentsData*>(aClosure);
72   if (aInStr != data->mStream) return NS_ERROR_FAILURE;
73   memcpy(&data->mBuffer[aOffset], aBuffer, aCount);
74   *aCountWritten = aCount;
75   return NS_OK;
76 }
77 
TEST(TestNonBlockingAsyncInputStream,ReadSegments)78 TEST(TestNonBlockingAsyncInputStream, ReadSegments)
79 {
80   nsCString data;
81   data.Assign("Hello world!");
82 
83   nsCOMPtr<nsIAsyncInputStream> async;
84   {
85     // Let's create a test string inputStream
86     nsCOMPtr<nsIInputStream> stream;
87     ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
88 
89     // Here the non-blocking stream.
90     ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
91                          stream.forget(), getter_AddRefs(async)));
92   }
93 
94   // Read works fine.
95   char buffer[1024];
96   uint32_t read = 0;
97   ReadSegmentsData closure(async, buffer);
98   ASSERT_EQ(NS_OK, async->ReadSegments(ReadSegmentsFunction, &closure,
99                                        sizeof(buffer), &read));
100   ASSERT_EQ(data.Length(), read);
101   ASSERT_TRUE(data.Equals(nsCString(buffer, read)));
102 }
103 
TEST(TestNonBlockingAsyncInputStream,AsyncWait_Simple)104 TEST(TestNonBlockingAsyncInputStream, AsyncWait_Simple)
105 {
106   nsCString data;
107   data.Assign("Hello world!");
108 
109   nsCOMPtr<nsIAsyncInputStream> async;
110   {
111     // Let's create a test string inputStream
112     nsCOMPtr<nsIInputStream> stream;
113     ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
114 
115     // Here the non-blocking stream.
116     ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
117                          stream.forget(), getter_AddRefs(async)));
118   }
119   ASSERT_TRUE(!!async);
120 
121   // Testing ::Available()
122   uint64_t length;
123   ASSERT_EQ(NS_OK, async->Available(&length));
124   ASSERT_EQ(data.Length(), length);
125 
126   // Testing ::AsyncWait - without EventTarget
127   RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
128 
129   ASSERT_EQ(NS_OK, async->AsyncWait(cb, 0, 0, nullptr));
130   ASSERT_TRUE(cb->Called());
131 
132   // Testing ::AsyncWait - with EventTarget
133   cb = new testing::InputStreamCallback();
134   nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
135 
136   ASSERT_EQ(NS_OK, async->AsyncWait(cb, 0, 0, thread));
137   ASSERT_FALSE(cb->Called());
138   MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
139       "xpcom:TEST(TestNonBlockingAsyncInputStream, AsyncWait_Simple)"_ns,
140       [&]() { return cb->Called(); }));
141   ASSERT_TRUE(cb->Called());
142 
143   // Read works fine.
144   char buffer[1024];
145   uint32_t read = 0;
146   ASSERT_EQ(NS_OK, async->Read(buffer, sizeof(buffer), &read));
147   ASSERT_EQ(data.Length(), read);
148   ASSERT_TRUE(data.Equals(nsCString(buffer, read)));
149 }
150 
TEST(TestNonBlockingAsyncInputStream,AsyncWait_ClosureOnly_withoutEventTarget)151 TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withoutEventTarget)
152 {
153   nsCString data;
154   data.Assign("Hello world!");
155 
156   nsCOMPtr<nsIAsyncInputStream> async;
157   {
158     // Let's create a test string inputStream
159     nsCOMPtr<nsIInputStream> stream;
160     ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
161 
162     // Here the non-blocking stream.
163     ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
164                          stream.forget(), getter_AddRefs(async)));
165   }
166   ASSERT_TRUE(!!async);
167 
168   // Testing ::AsyncWait - no eventTarget
169   RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
170 
171   ASSERT_EQ(NS_OK, async->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY,
172                                     0, nullptr));
173 
174   ASSERT_FALSE(cb->Called());
175   ASSERT_EQ(NS_OK, async->Close());
176   ASSERT_TRUE(cb->Called());
177 }
178 
TEST(TestNonBlockingAsyncInputStream,AsyncWait_ClosureOnly_withEventTarget)179 TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withEventTarget)
180 {
181   nsCString data;
182   data.Assign("Hello world!");
183 
184   nsCOMPtr<nsIAsyncInputStream> async;
185   {
186     // Let's create a test string inputStream
187     nsCOMPtr<nsIInputStream> stream;
188     ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
189 
190     // Here the non-blocking stream.
191     ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
192                          stream.forget(), getter_AddRefs(async)));
193   }
194   ASSERT_TRUE(!!async);
195 
196   // Testing ::AsyncWait - with EventTarget
197   RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
198   nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
199 
200   ASSERT_EQ(NS_OK, async->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY,
201                                     0, thread));
202 
203   ASSERT_FALSE(cb->Called());
204   ASSERT_EQ(NS_OK, async->Close());
205   ASSERT_FALSE(cb->Called());
206 
207   MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
208       "xpcom:TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withEventTarget)"_ns,
209       [&]() { return cb->Called(); }));
210   ASSERT_TRUE(cb->Called());
211 }
212 
TEST(TestNonBlockingAsyncInputStream,Helper)213 TEST(TestNonBlockingAsyncInputStream, Helper)
214 {
215   nsCString data;
216   data.Assign("Hello world!");
217 
218   nsCOMPtr<nsIAsyncInputStream> async;
219   {
220     // Let's create a test string inputStream
221     nsCOMPtr<nsIInputStream> stream;
222     ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
223 
224     // Here the non-blocking stream.
225     ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
226                          stream.forget(), getter_AddRefs(async)));
227   }
228   ASSERT_TRUE(!!async);
229 
230   // This should return the same object because async is already non-blocking
231   // and async.
232   nsCOMPtr<nsIAsyncInputStream> result;
233   nsCOMPtr<nsIAsyncInputStream> asyncTmp = async;
234   ASSERT_EQ(NS_OK, NS_MakeAsyncNonBlockingInputStream(asyncTmp.forget(),
235                                                       getter_AddRefs(result)));
236   ASSERT_EQ(async, result);
237 
238   // This will use NonBlockingAsyncInputStream wrapper.
239   {
240     nsCOMPtr<nsIInputStream> stream;
241     ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
242     ASSERT_EQ(NS_OK, NS_MakeAsyncNonBlockingInputStream(
243                          stream.forget(), getter_AddRefs(result)));
244   }
245   ASSERT_TRUE(async != result);
246   ASSERT_TRUE(async);
247 }
248 
249 class QIInputStream final : public nsIInputStream,
250                             public nsICloneableInputStream,
251                             public nsIIPCSerializableInputStream,
252                             public nsISeekableStream {
253  public:
254   NS_DECL_ISUPPORTS
255 
QIInputStream(bool aNonBlockingError,bool aCloneable,bool aIPCSerializable,bool aSeekable)256   QIInputStream(bool aNonBlockingError, bool aCloneable, bool aIPCSerializable,
257                 bool aSeekable)
258       : mNonBlockingError(aNonBlockingError),
259         mCloneable(aCloneable),
260         mIPCSerializable(aIPCSerializable),
261         mSeekable(aSeekable) {}
262 
263   // nsIInputStream
Close()264   NS_IMETHOD Close() override { return NS_ERROR_NOT_IMPLEMENTED; }
Available(uint64_t *)265   NS_IMETHOD Available(uint64_t*) override { return NS_ERROR_NOT_IMPLEMENTED; }
Read(char *,uint32_t,uint32_t *)266   NS_IMETHOD Read(char*, uint32_t, uint32_t*) override {
267     return NS_ERROR_NOT_IMPLEMENTED;
268   }
ReadSegments(nsWriteSegmentFun,void *,uint32_t,uint32_t *)269   NS_IMETHOD ReadSegments(nsWriteSegmentFun, void*, uint32_t,
270                           uint32_t*) override {
271     return NS_ERROR_NOT_IMPLEMENTED;
272   }
IsNonBlocking(bool * aNonBlocking)273   NS_IMETHOD IsNonBlocking(bool* aNonBlocking) override {
274     *aNonBlocking = true;
275     return mNonBlockingError ? NS_ERROR_FAILURE : NS_OK;
276   }
277 
278   // nsICloneableInputStream
GetCloneable(bool *)279   NS_IMETHOD GetCloneable(bool*) override { return NS_ERROR_NOT_IMPLEMENTED; }
Clone(nsIInputStream **)280   NS_IMETHOD Clone(nsIInputStream**) override {
281     return NS_ERROR_NOT_IMPLEMENTED;
282   }
283 
284   // nsIIPCSerializableInputStream
Serialize(mozilla::ipc::InputStreamParams &,FileDescriptorArray &,bool,uint32_t,uint32_t *,mozilla::ipc::ParentToChildStreamActorManager *)285   void Serialize(mozilla::ipc::InputStreamParams&, FileDescriptorArray&, bool,
286                  uint32_t, uint32_t*,
287                  mozilla::ipc::ParentToChildStreamActorManager*) override {}
Serialize(mozilla::ipc::InputStreamParams &,FileDescriptorArray &,bool,uint32_t,uint32_t *,mozilla::ipc::ChildToParentStreamActorManager *)288   void Serialize(mozilla::ipc::InputStreamParams&, FileDescriptorArray&, bool,
289                  uint32_t, uint32_t*,
290                  mozilla::ipc::ChildToParentStreamActorManager*) override {}
Deserialize(const mozilla::ipc::InputStreamParams &,const FileDescriptorArray &)291   bool Deserialize(const mozilla::ipc::InputStreamParams&,
292                    const FileDescriptorArray&) override {
293     return false;
294   }
295 
296   // nsISeekableStream
Seek(int32_t,int64_t)297   NS_IMETHOD Seek(int32_t, int64_t) override {
298     return NS_ERROR_NOT_IMPLEMENTED;
299   }
SetEOF()300   NS_IMETHOD SetEOF() override { return NS_ERROR_NOT_IMPLEMENTED; }
301 
302   // nsITellableStream
Tell(int64_t *)303   NS_IMETHOD Tell(int64_t*) override { return NS_ERROR_NOT_IMPLEMENTED; }
304 
305  private:
306   ~QIInputStream() = default;
307 
308   bool mNonBlockingError;
309   bool mCloneable;
310   bool mIPCSerializable;
311   bool mSeekable;
312 };
313 
314 NS_IMPL_ADDREF(QIInputStream);
315 NS_IMPL_RELEASE(QIInputStream);
316 
317 NS_INTERFACE_MAP_BEGIN(QIInputStream)
NS_INTERFACE_MAP_ENTRY(nsIInputStream)318   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
319   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, mCloneable)
320   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
321                                      mIPCSerializable)
322   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, mSeekable)
323   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, mSeekable)
324   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
325 NS_INTERFACE_MAP_END
326 
327 TEST(TestNonBlockingAsyncInputStream, QI)
328 {
329   // Let's test ::Create() returning error.
330 
331   nsCOMPtr<nsIAsyncInputStream> async;
332   {
333     nsCOMPtr<nsIInputStream> stream = new QIInputStream(true, true, true, true);
334 
335     ASSERT_EQ(NS_ERROR_FAILURE, NonBlockingAsyncInputStream::Create(
336                                     stream.forget(), getter_AddRefs(async)));
337   }
338 
339   // Let's test the QIs
340   for (int i = 0; i < 8; ++i) {
341     bool shouldBeCloneable = !!(i & 0x01);
342     bool shouldBeSerializable = !!(i & 0x02);
343     bool shouldBeSeekable = !!(i & 0x04);
344 
345     nsCOMPtr<nsICloneableInputStream> cloneable;
346     nsCOMPtr<nsIIPCSerializableInputStream> ipcSerializable;
347     nsCOMPtr<nsISeekableStream> seekable;
348 
349     {
350       nsCOMPtr<nsIInputStream> stream = new QIInputStream(
351           false, shouldBeCloneable, shouldBeSerializable, shouldBeSeekable);
352 
353       cloneable = do_QueryInterface(stream);
354       ASSERT_EQ(shouldBeCloneable, !!cloneable);
355 
356       ipcSerializable = do_QueryInterface(stream);
357       ASSERT_EQ(shouldBeSerializable, !!ipcSerializable);
358 
359       seekable = do_QueryInterface(stream);
360       ASSERT_EQ(shouldBeSeekable, !!seekable);
361 
362       ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
363                            stream.forget(), getter_AddRefs(async)));
364     }
365 
366     // The returned async stream should be cloneable only if the underlying
367     // stream is.
368     cloneable = do_QueryInterface(async);
369     ASSERT_EQ(shouldBeCloneable, !!cloneable);
370 
371     // The returned async stream should be serializable only if the underlying
372     // stream is.
373     ipcSerializable = do_QueryInterface(async);
374     ASSERT_EQ(shouldBeSerializable, !!ipcSerializable);
375 
376     // The returned async stream should be seekable only if the underlying
377     // stream is.
378     seekable = do_QueryInterface(async);
379     ASSERT_EQ(shouldBeSeekable, !!seekable);
380   }
381 }
382