aboutsummaryrefslogtreecommitdiffhomepage
path: root/tests/FrontBufferedStreamTest.cpp
blob: 5452730ea4b6ffb3c0aa19a70d8a602c32f3cd84 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
/*
 * Copyright 2013 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkAutoMalloc.h"
#include "SkBitmap.h"
#include "SkCodec.h"
#include "SkFrontBufferedStream.h"
#include "SkRefCnt.h"
#include "SkStream.h"
#include "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 = SkFrontBufferedStream::Make(std::unique_ptr<SkStream>(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 = SkFrontBufferedStream::Make(std::unique_ptr<SkStream>(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 = SkFrontBufferedStream::Make(std::unique_ptr<SkStream>(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;
    typedef SkMemoryStream INHERITED;
};

// 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 = SkFrontBufferedStream::Make(std::unique_ptr<SkStream>(memStream),
                                                      bufferSize);
    test_hasLength(reporter, *bufferedStream.get(), *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);
}

// 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)
    {}

    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 = SkFrontBufferedStream::Make(std::unique_ptr<SkStream>(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 = 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 = SkFrontBufferedStream::Make(std::unique_ptr<SkStream>(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 = SkTMin(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 dummy 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 = SkFrontBufferedStream::Make(std::unique_ptr<SkStream>(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<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream)));
}