1/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "include/core/SkData.h"
9#include "include/core/SkStream.h"
10#include "src/codec/SkStreamBuffer.h"
11#include "src/utils/SkOSPath.h"
12
13#include "tests/FakeStreams.h"
14#include "tests/Test.h"
15
16static const char* gText = "Four score and seven years ago";
17
18static void test_get_data_at_position(skiatest::Reporter* r, SkStreamBuffer* buffer, size_t position,
19                                    size_t length) {
20    sk_sp<SkData> data = buffer->getDataAtPosition(position, length);
21    REPORTER_ASSERT(r, data);
22    if (data) {
23        REPORTER_ASSERT(r, !memcmp(data->data(), gText + position, length));
24    }
25}
26
27// Test buffering from the beginning, by different amounts.
28static void test_buffer_from_beginning(skiatest::Reporter* r, std::unique_ptr<SkStream> stream,
29                                       size_t length) {
30    if (!stream) {
31        return;
32    }
33    SkStreamBuffer buffer(std::move(stream));
34
35    // Buffer an arbitrary amount:
36    size_t buffered = length / 2;
37    REPORTER_ASSERT(r, buffer.buffer(buffered));
38    REPORTER_ASSERT(r, !memcmp(buffer.get(), gText, buffered));
39
40    // Buffering less is free:
41    REPORTER_ASSERT(r, buffer.buffer(buffered / 2));
42
43    // Buffer more should succeed:
44    REPORTER_ASSERT(r, buffer.buffer(length));
45    REPORTER_ASSERT(r, !memcmp(buffer.get(), gText, length));
46}
47
48// Test flushing the stream as we read.
49static void test_flushing(skiatest::Reporter* r, std::unique_ptr<SkStream> stream, size_t length,
50                          bool getDataAtPosition) {
51    if (!stream) {
52        return;
53    }
54    SkStreamBuffer buffer(std::move(stream));
55    const size_t step = 5;
56    for (size_t position = 0; position + step <= length; position += step) {
57        REPORTER_ASSERT(r, buffer.buffer(step));
58        REPORTER_ASSERT(r, buffer.markPosition() == position);
59
60        if (!getDataAtPosition) {
61            REPORTER_ASSERT(r, !memcmp(buffer.get(), gText + position, step));
62        }
63        buffer.flush();
64    }
65
66    REPORTER_ASSERT(r, !buffer.buffer(step));
67
68    if (getDataAtPosition) {
69        for (size_t position = 0; position + step <= length; position += step) {
70            test_get_data_at_position(r, &buffer, position, step);
71        }
72    }
73}
74
75DEF_TEST(StreamBuffer, r) {
76    const size_t size = strlen(gText);
77    sk_sp<SkData> data(SkData::MakeWithoutCopy(gText, size));
78
79    SkString tmpDir = skiatest::GetTmpDir();
80    const char* subdir = "streamBuffer.txt";
81    SkString path;
82
83    if (!tmpDir.isEmpty()) {
84        path = SkOSPath::Join(tmpDir.c_str(), subdir);
85        SkFILEWStream writer(path.c_str());
86        if (!writer.isValid()) {
87            ERRORF(r, "unable to write to '%s'\n", path.c_str());
88            return;
89        }
90        writer.write(gText, size);
91    }
92
93    struct Factory {
94        std::function<std::unique_ptr<SkStream>()>  createStream;
95        bool                                        skipIfNoTmpDir;
96    };
97
98    Factory factories[] = {
99        { [&data]() { return std::make_unique<SkMemoryStream>(data); },       false  },
100        { [&data]() { return std::make_unique<NotAssetMemStream>(data); },    false  },
101        { [&path]() { return path.isEmpty()
102                             ? nullptr
103                             : std::make_unique<SkFILEStream>(path.c_str()); }, true },
104    };
105
106    for (const Factory& f : factories) {
107        if (tmpDir.isEmpty() && f.skipIfNoTmpDir) {
108            continue;
109        }
110        test_buffer_from_beginning(r, f.createStream(), size);
111        test_flushing(r, f.createStream(), size, false);
112        test_flushing(r, f.createStream(), size, true);
113    }
114
115    // Stream that will receive more data. Will be owned by the SkStreamBuffer.
116    auto halting = std::make_unique<HaltingStream>(data, 6);
117    HaltingStream* peekHalting = halting.get();
118    SkStreamBuffer buffer(std::move(halting));
119
120    // Can only buffer less than what's available (6).
121    REPORTER_ASSERT(r, !buffer.buffer(7));
122    REPORTER_ASSERT(r, buffer.buffer(5));
123    REPORTER_ASSERT(r, !memcmp(buffer.get(), gText, 5));
124
125    // Add some more data. We can buffer and read all of it.
126    peekHalting->addNewData(8);
127    REPORTER_ASSERT(r, buffer.buffer(14));
128    REPORTER_ASSERT(r, !memcmp(buffer.get(), gText, 14));
129
130    // Flush the buffer, which moves the position.
131    buffer.flush();
132
133    // Add some data, and try to read more. Can only read what is
134    // available.
135    peekHalting->addNewData(9);
136    REPORTER_ASSERT(r, !buffer.buffer(13));
137    peekHalting->addNewData(4);
138    REPORTER_ASSERT(r, buffer.buffer(13));
139
140    // Do not call get on this data. We'll come back to this data after adding
141    // more.
142    buffer.flush();
143    const size_t remaining = size - 27;
144    REPORTER_ASSERT(r, remaining > 0);
145    peekHalting->addNewData(remaining);
146    REPORTER_ASSERT(r, buffer.buffer(remaining));
147    REPORTER_ASSERT(r, !memcmp(buffer.get(), gText + 27, remaining));
148
149    // Now go back to the data we skipped.
150    test_get_data_at_position(r, &buffer, 14, 13);
151}
152