// Copyright 2021 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_stream/stream.h" #include #include "pw_assert/check.h" #include "pw_bytes/array.h" #include "pw_bytes/span.h" #include "pw_containers/to_array.h" #include "pw_span/span.h" #include "pw_unit_test/framework.h" namespace pw::stream { namespace { static_assert(sizeof(Stream) <= 2 * sizeof(void*), "Stream should be no larger than two pointers (vtable pointer & " "packed members)"); class TestNonSeekableReader : public NonSeekableReader { private: StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); } }; class TestRelativeSeekableReader : public RelativeSeekableReader { private: StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); } Status DoSeek(ptrdiff_t, Whence) override { return Status(); } }; class TestSeekableReader : public SeekableReader { private: StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); } Status DoSeek(ptrdiff_t, Whence) override { return Status(); } }; class TestNonSeekableWriter : public NonSeekableWriter { private: Status DoWrite(ConstByteSpan) override { return OkStatus(); } }; class TestRelativeSeekableWriter : public RelativeSeekableWriter { private: Status DoWrite(ConstByteSpan) override { return OkStatus(); } Status DoSeek(ptrdiff_t, Whence) override { return OkStatus(); } }; class TestSeekableWriter : public SeekableWriter { private: Status DoWrite(ConstByteSpan) override { return OkStatus(); } Status DoSeek(ptrdiff_t, Whence) override { return OkStatus(); } }; class TestNonSeekableReaderWriter : public NonSeekableReaderWriter { private: StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); } Status DoWrite(ConstByteSpan) override { return OkStatus(); } }; class TestRelativeSeekableReaderWriter : public RelativeSeekableReaderWriter { private: StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); } Status DoWrite(ConstByteSpan) override { return OkStatus(); } Status DoSeek(ptrdiff_t, Whence) override { return OkStatus(); } }; class TestSeekableReaderWriter : public SeekableReaderWriter { private: StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); } Status DoWrite(ConstByteSpan) override { return OkStatus(); } Status DoSeek(ptrdiff_t, Whence) override { return OkStatus(); } }; // Test ReaderWriter conversions to Reader/Writer. // clang-format off static_assert(std::is_convertible()); static_assert(std::is_convertible()); static_assert(!std::is_convertible()); static_assert(!std::is_convertible()); static_assert(!std::is_convertible()); static_assert(!std::is_convertible()); static_assert(std::is_convertible()); static_assert(std::is_convertible()); static_assert(std::is_convertible()); static_assert(std::is_convertible()); static_assert(!std::is_convertible()); static_assert(!std::is_convertible()); static_assert(std::is_convertible()); static_assert(std::is_convertible()); static_assert(std::is_convertible()); static_assert(std::is_convertible()); static_assert(std::is_convertible()); static_assert(std::is_convertible()); // clang-format on constexpr uint8_t kSeekable = Stream::kBeginning | Stream::kCurrent | Stream::kEnd; constexpr uint8_t kRelativeSeekable = Stream::kCurrent; constexpr uint8_t kNonSeekable = 0; enum Readable : bool { kNonReadable = false, kReadable = true }; enum Writable : bool { kNonWritable = false, kWritable = true }; template void TestStreamImpl() { T derived_stream; Stream& stream = derived_stream; // Check stream properties ASSERT_EQ(writable, stream.writable()); ASSERT_EQ(readable, stream.readable()); ASSERT_EQ((seekable & Stream::kBeginning) != 0, stream.seekable(Stream::kBeginning)); ASSERT_EQ((seekable & Stream::kCurrent) != 0, stream.seekable(Stream::kCurrent)); ASSERT_EQ((seekable & Stream::kEnd) != 0, stream.seekable(Stream::kEnd)); ASSERT_EQ(seekable != kNonSeekable, stream.seekable()); // Check Read()/Write()/Seek() ASSERT_EQ(readable ? OkStatus() : Status::Unimplemented(), stream.Read({}).status()); ASSERT_EQ(writable ? OkStatus() : Status::Unimplemented(), stream.Write({})); ASSERT_EQ(seekable ? OkStatus() : Status::Unimplemented(), stream.Seek(0)); // Check ConservativeLimits() ASSERT_EQ(readable ? Stream::kUnlimited : 0, stream.ConservativeReadLimit()); ASSERT_EQ(writable ? Stream::kUnlimited : 0, stream.ConservativeWriteLimit()); } TEST(Stream, NonSeekableReader) { TestStreamImpl(); } TEST(Stream, RelativeSeekableReader) { TestStreamImpl(); } TEST(Stream, SeekableReader) { TestStreamImpl(); } TEST(Stream, NonSeekableWriter) { TestStreamImpl(); } TEST(Stream, RelativeSeekableWriter) { TestStreamImpl(); } TEST(Stream, SeekableWriter) { TestStreamImpl(); } TEST(Stream, NonSeekableReaderWriter) { TestStreamImpl(); } TEST(Stream, RelativeSeekableReaderWriter) { TestStreamImpl(); } TEST(Stream, SeekableReaderWriter) { TestStreamImpl(); } class TestFragmentedReader : public NonSeekableReader { public: TestFragmentedReader(ConstByteSpan data, span frags) : data_(data), frags_(frags), current_frag_(frags.begin()) { size_t frags_sum = 0; for (const auto& frag : frags) { frags_sum += frag.size(); } PW_CHECK_UINT_LE(frags_sum, data.size()); } private: StatusWithSize DoRead(ByteSpan dest) override { // Each fragment is consumed entirely on each read. PW_CHECK_UINT_GE(dest.size(), current_frag_->size()); PW_CHECK(current_frag_ != frags_.end()); auto frag = current_frag_++; auto source = data_.subspan(data_offset_, frag->size()); data_offset_ += frag->size(); std::copy(source.begin(), source.end(), dest.begin()); return *frag; } ConstByteSpan data_; span frags_; decltype(frags_)::iterator current_frag_; size_t data_offset_ = 0; }; TEST(Stream, ReadExact_Works) { constexpr auto kData = bytes:: Array<0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A>(); auto frags = containers::to_array({ StatusWithSize(3), // 0x00, 0x01, 0x02 StatusWithSize(5), // 0x03, 0x04, 0x05, 0x06, 0x07 StatusWithSize(1), // 0x08 StatusWithSize(2), // 0x09, 0x0A }); TestFragmentedReader reader(kData, frags); std::array dest; auto result = reader.ReadExact(dest); PW_TEST_ASSERT_OK(result); EXPECT_EQ(result->data(), dest.data()); EXPECT_EQ(result->size(), dest.size()); EXPECT_TRUE(std::equal(result->begin(), result->end(), kData.begin())); } TEST(Stream, ReadExact_HandlesError) { constexpr auto kData = bytes:: Array<0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A>(); auto frags = containers::to_array({ StatusWithSize(3), // 0x00, 0x01, 0x02 StatusWithSize(5), // 0x03, 0x04, 0x05, 0x06, 0x07 StatusWithSize::Internal(1), // 0x08 StatusWithSize(2), // 0x09, 0x0A }); TestFragmentedReader reader(kData, frags); std::array dest; auto result = reader.ReadExact(dest); EXPECT_EQ(result.status(), Status::Internal()); } } // namespace } // namespace pw::stream