/* * Copyright 2013 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ // Make sure SkUserConfig.h is included so #defines are available on // Android. #include "include/core/SkTypes.h" #ifdef SK_ENABLE_ANDROID_UTILS #include "client_utils/android/FrontBufferedStream.h" #include "include/codec/SkCodec.h" #include "include/core/SkBitmap.h" #include "include/core/SkRefCnt.h" #include "include/core/SkStream.h" #include "src/core/SkAutoMalloc.h" #include "tests/Test.h" static void test_read(skiatest::Reporter* reporter, SkStream* bufferedStream, const void* expectations, size_t bytesToRead) { // output for reading bufferedStream. SkAutoMalloc storage(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); } static void test_rewind(skiatest::Reporter* reporter, SkStream* bufferedStream, bool shouldSucceed) { const bool success = bufferedStream->rewind(); 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. const char gAbcs[] = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx"; // Tests reading the stream across boundaries of what has been buffered so far and what // the total buffer size is. static void test_incremental_buffering(skiatest::Reporter* reporter, size_t bufferSize) { // NOTE: For this and other tests in this file, we cheat and continue to refer to the // wrapped stream, but that's okay because we know the wrapping stream has not been // deleted yet (and we only call const methods in it). SkMemoryStream* memStream = SkMemoryStream::MakeDirect(gAbcs, strlen(gAbcs)).release(); auto bufferedStream = android::skia::FrontBufferedStream::Make( std::unique_ptr(memStream), bufferSize); test_hasLength(reporter, *bufferedStream, *memStream); // First, test reading less than the max buffer size. test_read(reporter, bufferedStream.get(), gAbcs, bufferSize / 2); // Now test rewinding back to the beginning and reading less than what was // already buffered. test_rewind(reporter, bufferedStream.get(), true); test_read(reporter, bufferedStream.get(), gAbcs, bufferSize / 4); // Now test reading part of what was buffered, and buffering new data. test_read(reporter, bufferedStream.get(), gAbcs + bufferSize / 4, bufferSize / 2); // Now test reading what was buffered, buffering new data, and // reading directly from the stream. test_rewind(reporter, bufferedStream.get(), true); test_read(reporter, bufferedStream.get(), gAbcs, bufferSize << 1); // We have reached the end of the buffer, so rewinding will fail. // This test assumes that the stream is larger than the buffer; otherwise the // result of rewind should be true. test_rewind(reporter, bufferedStream.get(), false); } static void test_perfectly_sized_buffer(skiatest::Reporter* reporter, size_t bufferSize) { SkMemoryStream* memStream = SkMemoryStream::MakeDirect(gAbcs, strlen(gAbcs)).release(); auto bufferedStream = android::skia::FrontBufferedStream::Make( std::unique_ptr(memStream), bufferSize); test_hasLength(reporter, *bufferedStream, *memStream); // Read exactly the amount that fits in the buffer. test_read(reporter, bufferedStream.get(), gAbcs, bufferSize); // Rewinding should succeed. test_rewind(reporter, bufferedStream.get(), true); // Once again reading buffered info should succeed test_read(reporter, bufferedStream.get(), gAbcs, bufferSize); // Read past the size of the buffer. At this point, we cannot return. test_read(reporter, bufferedStream.get(), gAbcs + memStream->getPosition(), 1); test_rewind(reporter, bufferedStream.get(), false); } static void test_skipping(skiatest::Reporter* reporter, size_t bufferSize) { SkMemoryStream* memStream = SkMemoryStream::MakeDirect(gAbcs, strlen(gAbcs)).release(); auto bufferedStream = android::skia::FrontBufferedStream::Make( std::unique_ptr(memStream), bufferSize); test_hasLength(reporter, *bufferedStream, *memStream); // Skip half the buffer. bufferedStream->skip(bufferSize / 2); // Rewind, then read part of the buffer, which should have been read. test_rewind(reporter, bufferedStream.get(), true); test_read(reporter, bufferedStream.get(), gAbcs, bufferSize / 4); // Now skip beyond the buffered piece, but still within the total buffer. bufferedStream->skip(bufferSize / 2); // Test that reading will still work. test_read(reporter, bufferedStream.get(), gAbcs + memStream->getPosition(), bufferSize / 4); test_rewind(reporter, bufferedStream.get(), true); test_read(reporter, bufferedStream.get(), gAbcs, bufferSize); } // A custom class whose isAtEnd behaves the way Android's stream does - since it is an adaptor to a // Java InputStream, it does not know that it is at the end until it has attempted to read beyond // the end and failed. Used by test_read_beyond_buffer. class AndroidLikeMemoryStream : public SkMemoryStream { public: AndroidLikeMemoryStream(void* data, size_t size, bool ownMemory) : INHERITED(data, size, ownMemory) , fIsAtEnd(false) {} size_t read(void* dst, size_t requested) override { size_t bytesRead = this->INHERITED::read(dst, requested); if (bytesRead < requested) { fIsAtEnd = true; } return bytesRead; } bool isAtEnd() const override { return fIsAtEnd; } private: bool fIsAtEnd; using INHERITED = SkMemoryStream; }; // This test ensures that buffering the exact length of the stream and attempting to read beyond it // does not invalidate the buffer. static void test_read_beyond_buffer(skiatest::Reporter* reporter, size_t bufferSize) { // Use a stream that behaves like Android's stream. AndroidLikeMemoryStream* memStream = new AndroidLikeMemoryStream((void*)gAbcs, bufferSize, false); // Create a buffer that matches the length of the stream. auto bufferedStream = android::skia::FrontBufferedStream::Make( std::unique_ptr(memStream), bufferSize); test_hasLength(reporter, *bufferedStream, *memStream); // Attempt to read one more than the bufferSize test_read(reporter, bufferedStream.get(), gAbcs, bufferSize + 1); test_rewind(reporter, bufferedStream.get(), true); // Ensure that the initial read did not invalidate the buffer. test_read(reporter, bufferedStream.get(), gAbcs, bufferSize); } // Mock 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) {} bool hasLength() const override { return fHasLength; } bool hasPosition() const override { return fHasPosition; } size_t read(void*, size_t) override { return 0; } bool isAtEnd() const 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 = new LengthOptionalStream(SkToBool(hasLen), SkToBool(hasPos)); auto buffered = android::skia::FrontBufferedStream::Make( std::unique_ptr(stream), bufferSize); test_hasLength(reporter, *buffered, *stream); } } } // Test using a stream with an initial offset. static void test_initial_offset(skiatest::Reporter* reporter, size_t bufferSize) { SkMemoryStream* memStream = new SkMemoryStream(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); auto bufferedStream = android::skia::FrontBufferedStream::Make( std::unique_ptr(memStream), bufferSize); // Since SkMemoryStream has a length, bufferedStream must also. REPORTER_ASSERT(reporter, bufferedStream->hasLength()); const size_t amountToRead = 10; const size_t bufferedLength = bufferedStream->getLength(); size_t currentPosition = 0; // 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.get(), gAbcs + arbitraryOffset + currentPosition, amountToRead); currentPosition = std::min(currentPosition + amountToRead, bufferedLength); REPORTER_ASSERT(reporter, memStream->getPosition() - arbitraryOffset == 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) { // Test 6 and 64, which are used by Android, as well as another arbitrary length. test_buffers(reporter, 6); test_buffers(reporter, 15); test_buffers(reporter, 64); } // Test that a FrontBufferedStream does not allow reading after the end of a stream. // This class is a mock SkStream which reports that it is at the end on the first // read (simulating a failure). Then it tracks whether someone calls read() again. class FailingStream : public SkStream { public: FailingStream() : fAtEnd(false) {} size_t read(void* buffer, size_t size) override { SkASSERT(!fAtEnd); fAtEnd = true; return 0; } bool isAtEnd() const override { return fAtEnd; } private: bool fAtEnd; }; DEF_TEST(ShortFrontBufferedStream, reporter) { FailingStream* failingStream = new FailingStream; auto stream = android::skia::FrontBufferedStream::Make( std::unique_ptr(failingStream), 64); // This will fail to create a codec. However, what we really want to test is that we // won't read past the end of the stream. std::unique_ptr codec(SkCodec::MakeFromStream(std::move(stream))); } #endif // SK_ENABLE_ANDROID_UTILS