1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <algorithm>
6
7#include "base/format_macros.h"
8#include "base/stringprintf.h"
9#include "net/base/net_errors.h"
10#include "net/http/http_util.h"
11#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
12#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrameClient.h"
13#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h"
14#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h"
15#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
16#include "webkit/glue/media/buffered_resource_loader.h"
17#include "webkit/mocks/mock_webframe.h"
18#include "webkit/mocks/mock_weburlloader.h"
19
20using ::testing::_;
21using ::testing::Assign;
22using ::testing::AtLeast;
23using ::testing::DeleteArg;
24using ::testing::DoAll;
25using ::testing::InSequence;
26using ::testing::Invoke;
27using ::testing::InvokeWithoutArgs;
28using ::testing::NotNull;
29using ::testing::Return;
30using ::testing::ReturnRef;
31using ::testing::SetArgumentPointee;
32using ::testing::StrictMock;
33using ::testing::NiceMock;
34using ::testing::WithArgs;
35
36using WebKit::WebURLError;
37using WebKit::WebFrameClient;
38using WebKit::WebURLResponse;
39using WebKit::WebView;
40
41namespace webkit_glue {
42
43static const char* kHttpUrl = "http://test";
44static const char kHttpRedirectToSameDomainUrl1[] = "http://test/ing";
45static const char kHttpRedirectToSameDomainUrl2[] = "http://test/ing2";
46static const char kHttpRedirectToDifferentDomainUrl1[] = "http://test2";
47static const char kHttpRedirectToDifferentDomainUrl2[] = "http://test2/ing";
48
49static const int kDataSize = 1024;
50static const int kHttpOK = 200;
51static const int kHttpPartialContent = 206;
52
53enum NetworkState {
54  NONE,
55  LOADED,
56  LOADING
57};
58
59// Submit a request completed event to the resource loader due to request
60// being canceled. Pretending the event is from external.
61ACTION_P(RequestCanceled, loader) {
62  WebURLError error;
63  error.reason = net::ERR_ABORTED;
64  error.domain = WebString::fromUTF8(net::kErrorDomain);
65  loader->didFail(NULL, error);
66}
67
68class BufferedResourceLoaderTest : public testing::Test {
69 public:
70  BufferedResourceLoaderTest() {
71    for (int i = 0; i < kDataSize; ++i)
72      data_[i] = i;
73  }
74
75  virtual ~BufferedResourceLoaderTest() {
76  }
77
78  void Initialize(const char* url, int first_position, int last_position) {
79    gurl_ = GURL(url);
80    first_position_ = first_position;
81    last_position_ = last_position;
82
83    frame_.reset(new NiceMock<MockWebFrame>());
84
85    url_loader_ = new NiceMock<MockWebURLLoader>();
86    loader_ = new BufferedResourceLoader(gurl_,
87                                         first_position_, last_position_);
88    loader_->SetURLLoaderForTest(url_loader_);
89  }
90
91  void SetLoaderBuffer(size_t forward_capacity, size_t backward_capacity) {
92    loader_->buffer_.reset(
93        new media::SeekableBuffer(backward_capacity, forward_capacity));
94  }
95
96  void Start() {
97    InSequence s;
98    EXPECT_CALL(*url_loader_, loadAsynchronously(_, loader_.get()));
99    loader_->Start(
100        NewCallback(this, &BufferedResourceLoaderTest::StartCallback),
101        NewCallback(this, &BufferedResourceLoaderTest::NetworkCallback),
102        frame_.get());
103  }
104
105  void FullResponse(int64 instance_size) {
106    FullResponse(instance_size, net::OK);
107  }
108
109  void FullResponse(int64 instance_size, int status) {
110    EXPECT_CALL(*this, StartCallback(status));
111    if (status != net::OK) {
112      EXPECT_CALL(*url_loader_, cancel())
113          .WillOnce(RequestCanceled(loader_));
114    }
115
116    WebURLResponse response(gurl_);
117    response.setHTTPHeaderField(WebString::fromUTF8("Content-Length"),
118                                WebString::fromUTF8(base::StringPrintf("%"
119                                    PRId64, instance_size)));
120    response.setExpectedContentLength(instance_size);
121    response.setHTTPStatusCode(kHttpOK);
122    loader_->didReceiveResponse(url_loader_, response);
123
124    if (status == net::OK) {
125      EXPECT_EQ(instance_size, loader_->content_length());
126      EXPECT_EQ(instance_size, loader_->instance_size());
127    }
128
129    EXPECT_FALSE(loader_->range_supported());
130  }
131
132  void PartialResponse(int64 first_position, int64 last_position,
133                       int64 instance_size) {
134    PartialResponse(first_position, last_position, instance_size, false, true);
135  }
136
137  void PartialResponse(int64 first_position, int64 last_position,
138                       int64 instance_size, bool chunked, bool accept_ranges) {
139    EXPECT_CALL(*this, StartCallback(net::OK));
140
141    WebURLResponse response(gurl_);
142    response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"),
143                                WebString::fromUTF8(base::StringPrintf("bytes "
144                                            "%" PRId64 "-%" PRId64 "/%" PRId64,
145                                            first_position,
146                                            last_position,
147                                            instance_size)));
148
149    // HTTP 1.1 doesn't permit Content-Length with Transfer-Encoding: chunked.
150    int64 content_length = -1;
151    if (chunked) {
152      response.setHTTPHeaderField(WebString::fromUTF8("Transfer-Encoding"),
153                                  WebString::fromUTF8("chunked"));
154    } else {
155      content_length = last_position - first_position + 1;
156    }
157    response.setExpectedContentLength(content_length);
158
159    // A server isn't required to return Accept-Ranges even though it might.
160    if (accept_ranges) {
161      response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"),
162                                  WebString::fromUTF8("bytes"));
163    }
164
165    response.setHTTPStatusCode(kHttpPartialContent);
166    loader_->didReceiveResponse(url_loader_, response);
167
168    // XXX: what's the difference between these two? For example in the chunked
169    // range request case, Content-Length is unspecified (because it's chunked)
170    // but Content-Range: a-b/c can be returned, where c == Content-Length
171    //
172    // Can we eliminate one?
173    EXPECT_EQ(content_length, loader_->content_length());
174    EXPECT_EQ(instance_size, loader_->instance_size());
175
176    // A valid partial response should always result in this being true.
177    EXPECT_TRUE(loader_->range_supported());
178  }
179
180  void Redirect(const char* url) {
181    GURL redirectUrl(url);
182    WebKit::WebURLRequest newRequest(redirectUrl);
183    WebKit::WebURLResponse redirectResponse(gurl_);
184
185    loader_->willSendRequest(url_loader_, newRequest, redirectResponse);
186
187    MessageLoop::current()->RunAllPending();
188  }
189
190  void StopWhenLoad() {
191    InSequence s;
192    EXPECT_CALL(*url_loader_, cancel())
193        .WillOnce(RequestCanceled(loader_));
194    loader_->Stop();
195    loader_ = NULL;
196  }
197
198  // Helper method to write to |loader_| from |data_|.
199  void WriteLoader(int position, int size) {
200    EXPECT_CALL(*this, NetworkCallback())
201        .RetiresOnSaturation();
202    loader_->didReceiveData(url_loader_,
203                            reinterpret_cast<char*>(data_ + position),
204                            size,
205                            size);
206  }
207
208  // Helper method to read from |loader_|.
209  void ReadLoader(int64 position, int size, uint8* buffer) {
210    loader_->Read(position, size, buffer,
211                  NewCallback(this, &BufferedResourceLoaderTest::ReadCallback));
212  }
213
214  // Verifis that data in buffer[0...size] is equal to data_[pos...pos+size].
215  void VerifyBuffer(uint8* buffer, int pos, int size) {
216    EXPECT_EQ(0, memcmp(buffer, data_ + pos, size));
217  }
218
219  void ConfirmLoaderDeferredState(bool expectedVal) {
220    EXPECT_EQ(loader_->deferred_, expectedVal);
221  }
222
223  MOCK_METHOD1(StartCallback, void(int error));
224  MOCK_METHOD1(ReadCallback, void(int error));
225  MOCK_METHOD0(NetworkCallback, void());
226
227 protected:
228  GURL gurl_;
229  int64 first_position_;
230  int64 last_position_;
231
232  scoped_refptr<BufferedResourceLoader> loader_;
233  NiceMock<MockWebURLLoader>* url_loader_;
234  scoped_ptr<NiceMock<MockWebFrame> > frame_;
235
236  uint8 data_[kDataSize];
237
238 private:
239  DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest);
240};
241
242TEST_F(BufferedResourceLoaderTest, StartStop) {
243  Initialize(kHttpUrl, -1, -1);
244  Start();
245  StopWhenLoad();
246}
247
248// Tests that a bad HTTP response is recived, e.g. file not found.
249TEST_F(BufferedResourceLoaderTest, BadHttpResponse) {
250  Initialize(kHttpUrl, -1, -1);
251  Start();
252
253  EXPECT_CALL(*this, StartCallback(net::ERR_FAILED));
254  EXPECT_CALL(*url_loader_, cancel())
255      .WillOnce(RequestCanceled(loader_));
256
257  WebURLResponse response(gurl_);
258  response.setHTTPStatusCode(404);
259  response.setHTTPStatusText("Not Found\n");
260  loader_->didReceiveResponse(url_loader_, response);
261}
262
263// Tests that partial content is requested but not fulfilled.
264TEST_F(BufferedResourceLoaderTest, NotPartialResponse) {
265  Initialize(kHttpUrl, 100, -1);
266  Start();
267  FullResponse(1024, net::ERR_INVALID_RESPONSE);
268}
269
270// Tests that a 200 response is received.
271TEST_F(BufferedResourceLoaderTest, FullResponse) {
272  Initialize(kHttpUrl, -1, -1);
273  Start();
274  FullResponse(1024);
275  StopWhenLoad();
276}
277
278// Tests that a partial content response is received.
279TEST_F(BufferedResourceLoaderTest, PartialResponse) {
280  Initialize(kHttpUrl, 100, 200);
281  Start();
282  PartialResponse(100, 200, 1024);
283  StopWhenLoad();
284}
285
286TEST_F(BufferedResourceLoaderTest, PartialResponse_Chunked) {
287  Initialize(kHttpUrl, 100, 200);
288  Start();
289  PartialResponse(100, 200, 1024, true, true);
290  StopWhenLoad();
291}
292
293TEST_F(BufferedResourceLoaderTest, PartialResponse_NoAcceptRanges) {
294  Initialize(kHttpUrl, 100, 200);
295  Start();
296  PartialResponse(100, 200, 1024, false, false);
297  StopWhenLoad();
298}
299
300TEST_F(BufferedResourceLoaderTest, PartialResponse_ChunkedNoAcceptRanges) {
301  Initialize(kHttpUrl, 100, 200);
302  Start();
303  PartialResponse(100, 200, 1024, true, false);
304  StopWhenLoad();
305}
306
307// Tests that an invalid partial response is received.
308TEST_F(BufferedResourceLoaderTest, InvalidPartialResponse) {
309  Initialize(kHttpUrl, 0, 10);
310  Start();
311
312  EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE));
313  EXPECT_CALL(*url_loader_, cancel())
314      .WillOnce(RequestCanceled(loader_));
315
316  WebURLResponse response(gurl_);
317  response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"),
318                              WebString::fromUTF8(base::StringPrintf("bytes "
319                                  "%d-%d/%d", 1, 10, 1024)));
320  response.setExpectedContentLength(10);
321  response.setHTTPStatusCode(kHttpPartialContent);
322  loader_->didReceiveResponse(url_loader_, response);
323}
324
325// Tests the logic of sliding window for data buffering and reading.
326TEST_F(BufferedResourceLoaderTest, BufferAndRead) {
327  Initialize(kHttpUrl, 10, 29);
328  loader_->UpdateDeferStrategy(BufferedResourceLoader::kThresholdDefer);
329  Start();
330  PartialResponse(10, 29, 30);
331
332  uint8 buffer[10];
333  InSequence s;
334
335  // Writes 10 bytes and read them back.
336  WriteLoader(10, 10);
337  EXPECT_CALL(*this, ReadCallback(10));
338  ReadLoader(10, 10, buffer);
339  VerifyBuffer(buffer, 10, 10);
340
341  // Writes 10 bytes and read 2 times.
342  WriteLoader(20, 10);
343  EXPECT_CALL(*this, ReadCallback(5));
344  ReadLoader(20, 5, buffer);
345  VerifyBuffer(buffer, 20, 5);
346  EXPECT_CALL(*this, ReadCallback(5));
347  ReadLoader(25, 5, buffer);
348  VerifyBuffer(buffer, 25, 5);
349
350  // Read backward within buffer.
351  EXPECT_CALL(*this, ReadCallback(10));
352  ReadLoader(10, 10, buffer);
353  VerifyBuffer(buffer, 10, 10);
354
355  // Read backward outside buffer.
356  EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
357  ReadLoader(9, 10, buffer);
358
359  // Response has completed.
360  EXPECT_CALL(*this, NetworkCallback());
361  loader_->didFinishLoading(url_loader_, 0);
362
363  // Try to read 10 from position 25 will just return with 5 bytes.
364  EXPECT_CALL(*this, ReadCallback(5));
365  ReadLoader(25, 10, buffer);
366  VerifyBuffer(buffer, 25, 5);
367
368  // Try to read outside buffered range after request has completed.
369  EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
370  ReadLoader(5, 10, buffer);
371
372  // Try to read beyond the instance size.
373  EXPECT_CALL(*this, ReadCallback(0));
374  ReadLoader(30, 10, buffer);
375}
376
377TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) {
378  Initialize(kHttpUrl, 10, 0x00FFFFFF);
379  loader_->UpdateDeferStrategy(BufferedResourceLoader::kThresholdDefer);
380  Start();
381  PartialResponse(10, 0x00FFFFFF, 0x01000000);
382
383  uint8 buffer[10];
384  InSequence s;
385
386  // Read very far aheard will get a cache miss.
387  EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
388  ReadLoader(0x00FFFFFF, 1, buffer);
389
390  // The following call will not call ReadCallback() because it is waiting for
391  // data to arrive.
392  ReadLoader(10, 10, buffer);
393
394  // Writing to loader will fulfill the read request.
395  EXPECT_CALL(*this, ReadCallback(10));
396  WriteLoader(10, 20);
397  VerifyBuffer(buffer, 10, 10);
398
399  // The following call cannot be fulfilled now.
400  ReadLoader(25, 10, buffer);
401
402  EXPECT_CALL(*this, ReadCallback(5));
403  EXPECT_CALL(*this, NetworkCallback());
404  loader_->didFinishLoading(url_loader_, 0);
405}
406
407TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) {
408  Initialize(kHttpUrl, 10, 29);
409  Start();
410  PartialResponse(10, 29, 30);
411
412  uint8 buffer[10];
413  InSequence s;
414
415  ReadLoader(10, 10, buffer);
416  EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED));
417  EXPECT_CALL(*this, NetworkCallback());
418  WebURLError error;
419  error.reason = net::ERR_FAILED;
420  loader_->didFail(url_loader_, error);
421}
422
423// Tests the data buffering logic of NeverDefer strategy.
424TEST_F(BufferedResourceLoaderTest, NeverDeferStrategy) {
425  Initialize(kHttpUrl, 10, 99);
426  SetLoaderBuffer(10, 20);
427  loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
428  Start();
429  PartialResponse(10, 99, 100);
430
431  uint8 buffer[10];
432
433  // Read past the buffer size; should not defer regardless.
434  WriteLoader(10, 10);
435  WriteLoader(20, 50);
436  ConfirmLoaderDeferredState(false);
437
438  // Should move past window.
439  EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
440  ReadLoader(10, 10, buffer);
441
442  StopWhenLoad();
443}
444
445// Tests the data buffering logic of ReadThenDefer strategy.
446TEST_F(BufferedResourceLoaderTest, ReadThenDeferStrategy) {
447  Initialize(kHttpUrl, 10, 99);
448  SetLoaderBuffer(10, 20);
449  loader_->UpdateDeferStrategy(BufferedResourceLoader::kReadThenDefer);
450  Start();
451  PartialResponse(10, 99, 100);
452
453  uint8 buffer[10];
454
455  // Make an outstanding read request.
456  // We should disable deferring after the read request, so expect
457  // a network event.
458  EXPECT_CALL(*this, NetworkCallback());
459  ReadLoader(10, 10, buffer);
460
461  // Receive almost enough data to cover, shouldn't defer.
462  WriteLoader(10, 9);
463  ConfirmLoaderDeferredState(false);
464
465  // As soon as we have received enough data to fulfill the read, defer.
466  EXPECT_CALL(*this, NetworkCallback());
467  EXPECT_CALL(*this, ReadCallback(10));
468  WriteLoader(19, 1);
469
470  ConfirmLoaderDeferredState(true);
471  VerifyBuffer(buffer, 10, 10);
472
473  StopWhenLoad();
474}
475
476// Tests the data buffering logic of ThresholdDefer strategy.
477TEST_F(BufferedResourceLoaderTest, ThresholdDeferStrategy) {
478  Initialize(kHttpUrl, 10, 99);
479  SetLoaderBuffer(10, 20);
480  loader_->UpdateDeferStrategy(BufferedResourceLoader::kThresholdDefer);
481  Start();
482  PartialResponse(10, 99, 100);
483
484  uint8 buffer[10];
485
486  WriteLoader(10, 5);
487  // Haven't reached threshold, don't defer.
488  ConfirmLoaderDeferredState(false);
489
490  // We're at the threshold now, let's defer.
491  EXPECT_CALL(*this, NetworkCallback());
492  WriteLoader(15, 5);
493  ConfirmLoaderDeferredState(true);
494
495  // Now we've read over half of the buffer, disable deferring.
496  EXPECT_CALL(*this, ReadCallback(6));
497  EXPECT_CALL(*this, NetworkCallback());
498  ReadLoader(10, 6, buffer);
499
500  ConfirmLoaderDeferredState(false);
501  VerifyBuffer(buffer, 10, 6);
502
503  StopWhenLoad();
504}
505
506// NOTE: This test will need to be reworked a little once
507// http://code.google.com/p/chromium/issues/detail?id=72578
508// is fixed.
509TEST_F(BufferedResourceLoaderTest, HasSingleOrigin) {
510  // Make sure no redirect case works as expected.
511  Initialize(kHttpUrl, -1, -1);
512  Start();
513  FullResponse(1024);
514  EXPECT_TRUE(loader_->HasSingleOrigin());
515  StopWhenLoad();
516
517  // Test redirect to the same domain.
518  Initialize(kHttpUrl, -1, -1);
519  Start();
520  Redirect(kHttpRedirectToSameDomainUrl1);
521  FullResponse(1024);
522  EXPECT_TRUE(loader_->HasSingleOrigin());
523  StopWhenLoad();
524
525  // Test redirect twice to the same domain.
526  Initialize(kHttpUrl, -1, -1);
527  Start();
528  Redirect(kHttpRedirectToSameDomainUrl1);
529  Redirect(kHttpRedirectToSameDomainUrl2);
530  FullResponse(1024);
531  EXPECT_TRUE(loader_->HasSingleOrigin());
532  StopWhenLoad();
533
534  // Test redirect to a different domain.
535  Initialize(kHttpUrl, -1, -1);
536  Start();
537  Redirect(kHttpRedirectToDifferentDomainUrl1);
538  EXPECT_FALSE(loader_->HasSingleOrigin());
539  StopWhenLoad();
540
541  // Test redirect to the same domain and then to a different domain.
542  Initialize(kHttpUrl, -1, -1);
543  Start();
544  Redirect(kHttpRedirectToSameDomainUrl1);
545  Redirect(kHttpRedirectToDifferentDomainUrl1);
546  EXPECT_FALSE(loader_->HasSingleOrigin());
547  StopWhenLoad();
548}
549
550// TODO(hclam): add unit test for defer loading.
551
552}  // namespace webkit_glue
553