Allow buffered stream to work with an offset.

If the stream being buffered is buffered from somewhere other than
the start, the SkFrontBufferedStream needs to take that into account
when reporting its length.

R=djsollen@google.com, bungeman@google.com, reed@google.com

Author: scroggo@google.com

Review URL: https://codereview.chromium.org/157103002

git-svn-id: http://skia.googlecode.com/svn/trunk@13388 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/tests/FrontBufferedStreamTest.cpp b/tests/FrontBufferedStreamTest.cpp
index 9f60854..6905bcd 100644
--- a/tests/FrontBufferedStreamTest.cpp
+++ b/tests/FrontBufferedStreamTest.cpp
@@ -16,7 +16,7 @@
     // output for reading bufferedStream.
     SkAutoMalloc storage(bytesToRead);
 
-    size_t bytesRead = bufferedStream->read(storage.get(), bytesToRead);
+    const size_t bytesRead = bufferedStream->read(storage.get(), bytesToRead);
     REPORTER_ASSERT(reporter, bytesRead == bytesToRead || bufferedStream->isAtEnd());
     REPORTER_ASSERT(reporter, memcmp(storage.get(), expectations, bytesRead) == 0);
 }
@@ -27,6 +27,20 @@
     REPORTER_ASSERT(reporter, success == shouldSucceed);
 }
 
+// Test that hasLength() returns the correct value, based on the stream
+// being wrapped. A length can only be known if the wrapped stream has a
+// length and it has a position (so its initial position can be taken into
+// account when computing the length).
+static void test_hasLength(skiatest::Reporter* reporter,
+                           const SkStream& bufferedStream,
+                           const SkStream& streamBeingBuffered) {
+    if (streamBeingBuffered.hasLength() && streamBeingBuffered.hasPosition()) {
+        REPORTER_ASSERT(reporter, bufferedStream.hasLength());
+    } else {
+        REPORTER_ASSERT(reporter, !bufferedStream.hasLength());
+    }
+}
+
 // All tests will buffer this string, and compare output to the original.
 // The string is long to ensure that all of our lengths being tested are
 // smaller than the string length.
@@ -38,6 +52,7 @@
     SkMemoryStream memStream(gAbcs, strlen(gAbcs), false);
 
     SkAutoTUnref<SkStream> bufferedStream(SkFrontBufferedStream::Create(&memStream, bufferSize));
+    test_hasLength(reporter, *bufferedStream.get(), memStream);
 
     // First, test reading less than the max buffer size.
     test_read(reporter, bufferedStream, gAbcs, bufferSize / 2);
@@ -64,6 +79,7 @@
 static void test_perfectly_sized_buffer(skiatest::Reporter* reporter, size_t bufferSize) {
     SkMemoryStream memStream(gAbcs, strlen(gAbcs), false);
     SkAutoTUnref<SkStream> bufferedStream(SkFrontBufferedStream::Create(&memStream, bufferSize));
+    test_hasLength(reporter, *bufferedStream.get(), memStream);
 
     // Read exactly the amount that fits in the buffer.
     test_read(reporter, bufferedStream, gAbcs, bufferSize);
@@ -82,6 +98,7 @@
 static void test_skipping(skiatest::Reporter* reporter, size_t bufferSize) {
     SkMemoryStream memStream(gAbcs, strlen(gAbcs), false);
     SkAutoTUnref<SkStream> bufferedStream(SkFrontBufferedStream::Create(&memStream, bufferSize));
+    test_hasLength(reporter, *bufferedStream.get(), memStream);
 
     // Skip half the buffer.
     bufferedStream->skip(bufferSize / 2);
@@ -134,6 +151,7 @@
 
     // Create a buffer that matches the length of the stream.
     SkAutoTUnref<SkStream> bufferedStream(SkFrontBufferedStream::Create(&memStream, bufferSize));
+    test_hasLength(reporter, *bufferedStream.get(), memStream);
 
     // Attempt to read one more than the bufferSize
     test_read(reporter, bufferedStream.get(), gAbcs, bufferSize + 1);
@@ -143,11 +161,86 @@
     test_read(reporter, bufferedStream, gAbcs, bufferSize);
 }
 
+// Dummy stream that optionally has a length and/or position. Tests that FrontBufferedStream's
+// length depends on the stream it's buffering having a length and position.
+class LengthOptionalStream : public SkStream {
+public:
+    LengthOptionalStream(bool hasLength, bool hasPosition)
+        : fHasLength(hasLength)
+        , fHasPosition(hasPosition)
+    {}
+
+    virtual bool hasLength() const SK_OVERRIDE {
+        return fHasLength;
+    }
+
+    virtual bool hasPosition() const SK_OVERRIDE {
+        return fHasPosition;
+    }
+
+    virtual size_t read(void*, size_t) SK_OVERRIDE {
+        return 0;
+    }
+
+    virtual bool isAtEnd() const SK_OVERRIDE {
+        return true;
+    }
+
+private:
+    const bool fHasLength;
+    const bool fHasPosition;
+};
+
+// Test all possible combinations of the wrapped stream having a length and a position.
+static void test_length_combos(skiatest::Reporter* reporter, size_t bufferSize) {
+    for (int hasLen = 0; hasLen <= 1; hasLen++) {
+        for (int hasPos = 0; hasPos <= 1; hasPos++) {
+            LengthOptionalStream stream((bool) hasLen, (bool) hasPos);
+            SkAutoTUnref<SkStream> buffered(SkFrontBufferedStream::Create(&stream, bufferSize));
+            test_hasLength(reporter, *buffered.get(), stream);
+        }
+    }
+}
+
+// Test using a stream with an initial offset.
+static void test_initial_offset(skiatest::Reporter* reporter, size_t bufferSize) {
+    SkMemoryStream memStream(gAbcs, strlen(gAbcs), false);
+
+    // Skip a few characters into the memStream, so that bufferedStream represents an offset into
+    // the stream it wraps.
+    const size_t arbitraryOffset = 17;
+    memStream.skip(arbitraryOffset);
+    SkAutoTUnref<SkStream> bufferedStream(SkFrontBufferedStream::Create(&memStream, bufferSize));
+
+    // Since SkMemoryStream has a length and a position, bufferedStream must also.
+    REPORTER_ASSERT(reporter, bufferedStream->hasLength());
+
+    const size_t amountToRead = 10;
+    const size_t bufferedLength = bufferedStream->getLength();
+    size_t currentPosition = bufferedStream->getPosition();
+    REPORTER_ASSERT(reporter, 0 == currentPosition);
+
+    // Read the stream in chunks. After each read, the position must match currentPosition,
+    // which sums the amount attempted to read, unless the end of the stream has been reached.
+    // Importantly, the end should not have been reached until currentPosition == bufferedLength.
+    while (currentPosition < bufferedLength) {
+        REPORTER_ASSERT(reporter, !bufferedStream->isAtEnd());
+        test_read(reporter, bufferedStream, gAbcs + arbitraryOffset + currentPosition,
+                  amountToRead);
+        currentPosition = SkTMin(currentPosition + amountToRead, bufferedLength);
+        REPORTER_ASSERT(reporter, bufferedStream->getPosition() == currentPosition);
+    }
+    REPORTER_ASSERT(reporter, bufferedStream->isAtEnd());
+    REPORTER_ASSERT(reporter, bufferedLength == currentPosition);
+}
+
 static void test_buffers(skiatest::Reporter* reporter, size_t bufferSize) {
     test_incremental_buffering(reporter, bufferSize);
     test_perfectly_sized_buffer(reporter, bufferSize);
     test_skipping(reporter, bufferSize);
     test_read_beyond_buffer(reporter, bufferSize);
+    test_length_combos(reporter, bufferSize);
+    test_initial_offset(reporter, bufferSize);
 }
 
 DEF_TEST(FrontBufferedStream, reporter) {