blob: 65c4f1bb0cfbc5a07216a9416de2c74ac11fde8f [file] [log] [blame]
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "google/protobuf/io/zero_copy_sink.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "absl/log/absl_check.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
namespace google {
namespace protobuf {
namespace io {
namespace zc_sink_internal {
namespace {
class ChunkedString {
public:
explicit ChunkedString(absl::string_view data, size_t skipped_patterns)
: data_(data), skipped_patterns_(skipped_patterns) {}
// Returns the next chunk; empty if out of chunks.
absl::string_view NextChunk() {
if (pattern_bit_idx_ == data_.size()) {
return "";
}
size_t start = pattern_bit_idx_;
do {
++pattern_bit_idx_;
} while (pattern_bit_idx_ < data_.size() &&
(pattern_ >> pattern_bit_idx_ & 1) == 0);
size_t end = pattern_bit_idx_;
return data_.substr(start, end - start);
}
// Resets the stream and runs the next pattern of splits.
bool NextPattern() {
pattern_ += skipped_patterns_;
if (pattern_ >= (1 << data_.size())) {
return false;
}
pattern_bit_idx_ = 0;
return true;
}
// prints out the pattern as a sequence of quoted strings.
std::string PatternAsQuotedString() {
std::string out;
size_t start = 0;
for (size_t i = 0; i <= data_.size(); ++i) {
if (i == data_.size() || (pattern_ >> start & 1) != 0) {
if (!out.empty()) {
absl::StrAppend(&out, " ");
}
absl::StrAppend(
&out, "\"",
absl::CHexEscape(std::string{data_.substr(start, i - start)}),
"\"");
start = i;
}
}
return out;
}
private:
absl::string_view data_;
size_t skipped_patterns_;
// pattern_ is a bitset indicating at which indices we insert a seam.
uint64_t pattern_ = 0;
size_t pattern_bit_idx_ = 0;
};
class PatternedOutputStream : public io::ZeroCopyOutputStream {
public:
explicit PatternedOutputStream(ChunkedString data) : data_(data) {}
bool Next(void** buffer, int* length) override {
absl::string_view segment;
if (!back_up_.empty()) {
segment = back_up_.back();
back_up_.pop_back();
} else {
segment_ = data_.NextChunk();
segment = segment_;
}
if (segment_.empty()) {
return false;
}
// TODO: This is only ever constructed in test code, and only
// from non-const bytes, so this is a valid cast. We need to do this since
// OSS proto does not yet have absl::Span; once we take a full Abseil
// dependency we should use that here instead.
*buffer = const_cast<char*>(segment.data());
*length = static_cast<int>(segment.size());
byte_count_ += static_cast<int64_t>(segment.size());
return true;
}
void BackUp(int length) override {
ABSL_CHECK(length <= static_cast<int>(segment_.size()));
size_t backup = segment_.size() - static_cast<size_t>(length);
back_up_.push_back(segment_.substr(backup));
segment_ = segment_.substr(0, backup);
byte_count_ -= static_cast<int64_t>(length);
}
int64_t ByteCount() const override { return byte_count_; }
private:
ChunkedString data_;
absl::string_view segment_;
std::vector<absl::string_view> back_up_;
int64_t byte_count_ = 0;
};
class ZeroCopyStreamByteSinkTest : public testing::Test {
protected:
std::array<char, 10> output_{};
absl::string_view output_view_{output_.data(), output_.size()};
ChunkedString output_chunks_{output_view_, 7};
};
TEST_F(ZeroCopyStreamByteSinkTest, WriteExact) {
do {
SCOPED_TRACE(output_chunks_.PatternAsQuotedString());
ChunkedString input("0123456789", 1);
do {
SCOPED_TRACE(input.PatternAsQuotedString());
output_ = {};
PatternedOutputStream output_stream(output_chunks_);
ZeroCopyStreamByteSink byte_sink(&output_stream);
SCOPED_TRACE(input.PatternAsQuotedString());
absl::string_view chunk;
while (!(chunk = input.NextChunk()).empty()) {
byte_sink.Append(chunk.data(), chunk.size());
}
} while (input.NextPattern());
ASSERT_EQ(output_view_, "0123456789");
} while (output_chunks_.NextPattern());
}
TEST_F(ZeroCopyStreamByteSinkTest, WriteShort) {
do {
SCOPED_TRACE(output_chunks_.PatternAsQuotedString());
ChunkedString input("012345678", 1);
do {
SCOPED_TRACE(input.PatternAsQuotedString());
output_ = {};
PatternedOutputStream output_stream(output_chunks_);
ZeroCopyStreamByteSink byte_sink(&output_stream);
SCOPED_TRACE(input.PatternAsQuotedString());
absl::string_view chunk;
while (!(chunk = input.NextChunk()).empty()) {
byte_sink.Append(chunk.data(), chunk.size());
}
} while (input.NextPattern());
ASSERT_EQ(output_view_, absl::string_view("012345678\0", 10));
} while (output_chunks_.NextPattern());
}
TEST_F(ZeroCopyStreamByteSinkTest, WriteLong) {
do {
SCOPED_TRACE(output_chunks_.PatternAsQuotedString());
ChunkedString input("0123456789A", 1);
do {
SCOPED_TRACE(input.PatternAsQuotedString());
output_ = {};
PatternedOutputStream output_stream(output_chunks_);
ZeroCopyStreamByteSink byte_sink(&output_stream);
SCOPED_TRACE(input.PatternAsQuotedString());
absl::string_view chunk;
while (!(chunk = input.NextChunk()).empty()) {
byte_sink.Append(chunk.data(), chunk.size());
}
} while (input.NextPattern());
ASSERT_EQ(output_view_, "0123456789");
} while (output_chunks_.NextPattern());
}
} // namespace
} // namespace zc_sink_internal
} // namespace io
} // namespace protobuf
} // namespace google