// Copyright 2023 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "iree/io/memory_stream.h"

#include <array>
#include <string>
#include <string_view>

#include "iree/base/api.h"
#include "iree/testing/gtest.h"
#include "iree/testing/status_matchers.h"

namespace {

using iree::Status;
using iree::StatusCode;
using iree::testing::status::StatusIs;
using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::Eq;

TEST(MemoryStreamTest, Wrap) {
  uint8_t data[5] = {0, 1, 2, 3, 4};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE, iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  EXPECT_EQ(iree_io_stream_mode(stream), IREE_IO_STREAM_MODE_READABLE);
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_EQ(iree_io_stream_length(stream), sizeof(data));
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, WrapEmpty) {
  uint8_t data[1] = {0};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE, iree_make_byte_span(data, 0),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  EXPECT_EQ(iree_io_stream_mode(stream), IREE_IO_STREAM_MODE_READABLE);
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_EQ(iree_io_stream_length(stream), 0);
  EXPECT_TRUE(iree_io_stream_is_eos(stream));

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, WrapReleaseCallback) {
  int callback_count = 0;
  iree_io_stream_release_callback_t release_callback = {
      +[](void* user_data, iree_io_stream_t* stream) {
        int* callback_count = (int*)user_data;
        ++(*callback_count);
      },
      &callback_count,
  };

  ASSERT_EQ(callback_count, 0);

  uint8_t data[1] = {0};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE, iree_make_byte_span(data, sizeof(data)),
      release_callback, iree_allocator_system(), &stream));
  ASSERT_EQ(callback_count, 0);

  iree_io_stream_release(stream);
  ASSERT_EQ(callback_count, 1);
}

TEST(MemoryStreamTest, SeekSet) {
  uint8_t data[5] = {0, 1, 2, 3, 4};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE, iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  // Streams start at origin 0.
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_EQ(iree_io_stream_length(stream), sizeof(data));
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // No-op seek to origin.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 0));
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Seek to end-of-stream.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET,
                                     iree_io_stream_length(stream)));
  EXPECT_EQ(iree_io_stream_offset(stream), iree_io_stream_length(stream));
  EXPECT_TRUE(iree_io_stream_is_eos(stream));

  // Seek to absolute offset 1.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 1));
  EXPECT_EQ(iree_io_stream_offset(stream), 1);
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Seek to absolute offset length-1 (last valid byte).
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 4));
  EXPECT_EQ(iree_io_stream_offset(stream), 4);
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Try seeking out of bounds.
  EXPECT_THAT(Status(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, -1)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 6)),
              StatusIs(StatusCode::kOutOfRange));

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, SeekFromCurrent) {
  uint8_t data[5] = {0, 1, 2, 3, 4};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE, iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  // Streams start at origin 0.
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_EQ(iree_io_stream_length(stream), sizeof(data));
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Seek to end-of-stream by jumping the full length.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_CURRENT,
                                     iree_io_stream_length(stream)));
  EXPECT_EQ(iree_io_stream_offset(stream), iree_io_stream_length(stream));
  EXPECT_TRUE(iree_io_stream_is_eos(stream));

  // Reset back to origin by seeking back the full length.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_CURRENT,
                                     -iree_io_stream_length(stream)));
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Seek forward to absolute position 1.
  IREE_EXPECT_OK(
      iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_CURRENT, 1));
  EXPECT_EQ(iree_io_stream_offset(stream), 1);
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // No-op seek to current location (absolute 1).
  IREE_EXPECT_OK(
      iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_CURRENT, 0));
  EXPECT_EQ(iree_io_stream_offset(stream), 1);
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Seek to absolute offset length-1 (last valid byte) - here (5-1) - 1 = 3.
  IREE_EXPECT_OK(
      iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_CURRENT, 3));
  EXPECT_EQ(iree_io_stream_offset(stream), 4);
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Seek forward 1 to absolute end-of-stream.
  IREE_EXPECT_OK(
      iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_CURRENT, 1));
  EXPECT_EQ(iree_io_stream_offset(stream), iree_io_stream_length(stream));
  EXPECT_TRUE(iree_io_stream_is_eos(stream));

  // Reset back to origin.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 0));

  // Try seeking out of bounds.
  EXPECT_THAT(Status(iree_io_stream_seek(
                  stream, IREE_IO_STREAM_SEEK_FROM_CURRENT, -100)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(Status(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 600)),
              StatusIs(StatusCode::kOutOfRange));

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, SeekFromEnd) {
  uint8_t data[5] = {0, 1, 2, 3, 4};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE, iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  // Streams start at origin 0.
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_EQ(iree_io_stream_length(stream), sizeof(data));
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Jump to end-of-stream.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, 0));
  EXPECT_EQ(iree_io_stream_offset(stream), iree_io_stream_length(stream));
  EXPECT_TRUE(iree_io_stream_is_eos(stream));

  // Reset back to origin by seeking back the full length.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END,
                                     -iree_io_stream_length(stream)));
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Seek to absolute offset length-1 (last valid byte) - here 5 - 1 = 4.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, -1));
  EXPECT_EQ(iree_io_stream_offset(stream), 4);
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Reset back to origin.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 0));

  // Try seeking out of bounds.
  EXPECT_THAT(
      Status(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, -100)),
      StatusIs(StatusCode::kOutOfRange));
  EXPECT_THAT(
      Status(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, 600)),
      StatusIs(StatusCode::kOutOfRange));

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, SeekToAlignment) {
  uint8_t data[5] = {0, 1, 2, 3, 4};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE, iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  // Streams start at origin 0.
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_EQ(iree_io_stream_length(stream), sizeof(data));
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  // Alignment must be a power of two.
  EXPECT_THAT(Status(iree_io_stream_seek_to_alignment(stream, 3)),
              StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(Status(iree_io_stream_seek_to_alignment(stream, 63)),
              StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(Status(iree_io_stream_seek_to_alignment(stream, -2)),
              StatusIs(StatusCode::kInvalidArgument));

  // Alignment at 0 should always be ok.
  IREE_EXPECT_OK(iree_io_stream_seek_to_alignment(stream, 0));
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  IREE_EXPECT_OK(iree_io_stream_seek_to_alignment(stream, 1));
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  IREE_EXPECT_OK(iree_io_stream_seek_to_alignment(stream, 2));
  EXPECT_EQ(iree_io_stream_offset(stream), 0);

  // Seek forward to an unaligned absolute offset 1.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 1));
  EXPECT_EQ(iree_io_stream_offset(stream), 1);

  // Seek forward to alignment 2, which should be absolute offset 2.
  IREE_EXPECT_OK(iree_io_stream_seek_to_alignment(stream, 2));
  EXPECT_EQ(iree_io_stream_offset(stream), 2);

  // Alignment that matches the current offset (2) should be a no-op.
  IREE_EXPECT_OK(iree_io_stream_seek_to_alignment(stream, 2));
  EXPECT_EQ(iree_io_stream_offset(stream), 2);

  // Align up from an aligned value.
  IREE_EXPECT_OK(iree_io_stream_seek_to_alignment(stream, 4));
  EXPECT_EQ(iree_io_stream_offset(stream), 4);

  // Try aligning off the end of the stream.
  EXPECT_THAT(Status(iree_io_stream_seek_to_alignment(stream, 16)),
              StatusIs(StatusCode::kOutOfRange));

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, ReadUpTo) {
  uint8_t data[5] = {0, 1, 2, 3, 4};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE, iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  // Streams start at origin 0.
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_EQ(iree_io_stream_length(stream), sizeof(data));
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  uint8_t read_buffer[64] = {0xDD};
  iree_host_size_t read_length = 0;

  // Reads of zero length should no-op.
  IREE_EXPECT_OK(iree_io_stream_read(stream, 0, read_buffer, &read_length));
  EXPECT_EQ(read_length, 0);
  EXPECT_EQ(iree_io_stream_offset(stream), 0);

  // Reads should advance the stream offset.
  memset(read_buffer, 0xDD, sizeof(read_buffer));
  IREE_EXPECT_OK(iree_io_stream_read(stream, 1, read_buffer, &read_length));
  EXPECT_EQ(read_length, 1);
  EXPECT_EQ(iree_io_stream_offset(stream), 1);
  EXPECT_EQ(read_buffer[0], 0);
  EXPECT_EQ(read_buffer[1], 0xDD);

  // Read another chunk of 2 bytes.
  memset(read_buffer, 0xDD, sizeof(read_buffer));
  IREE_EXPECT_OK(iree_io_stream_read(stream, 2, read_buffer, &read_length));
  EXPECT_EQ(read_length, 2);
  EXPECT_EQ(iree_io_stream_offset(stream), 3);
  EXPECT_EQ(read_buffer[0], 1);
  EXPECT_EQ(read_buffer[1], 2);
  EXPECT_EQ(read_buffer[2], 0xDD);

  // Read up to the end of the stream (2 bytes remaining) by reading over.
  memset(read_buffer, 0xDD, sizeof(read_buffer));
  IREE_EXPECT_OK(iree_io_stream_read(stream, sizeof(read_buffer), read_buffer,
                                     &read_length));
  EXPECT_EQ(read_length, 2);
  EXPECT_EQ(iree_io_stream_offset(stream), iree_io_stream_length(stream));
  EXPECT_TRUE(iree_io_stream_is_eos(stream));
  EXPECT_EQ(read_buffer[0], 3);
  EXPECT_EQ(read_buffer[1], 4);
  EXPECT_EQ(read_buffer[2], 0xDD);

  // Reading from the end of the stream should be a no-op.
  memset(read_buffer, 0xDD, sizeof(read_buffer));
  IREE_EXPECT_OK(iree_io_stream_read(stream, sizeof(read_buffer), read_buffer,
                                     &read_length));
  EXPECT_EQ(read_length, 0);
  EXPECT_EQ(iree_io_stream_offset(stream), iree_io_stream_length(stream));
  EXPECT_TRUE(iree_io_stream_is_eos(stream));
  EXPECT_EQ(read_buffer[0], 0xDD);

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, ReadExact) {
  uint8_t data[5] = {0, 1, 2, 3, 4};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE, iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  // Streams start at origin 0.
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_EQ(iree_io_stream_length(stream), sizeof(data));
  EXPECT_FALSE(iree_io_stream_is_eos(stream));

  uint8_t read_buffer[64] = {0xDD};

  // Reads of zero length should no-op.
  IREE_EXPECT_OK(iree_io_stream_read(stream, 0, read_buffer, NULL));
  EXPECT_EQ(iree_io_stream_offset(stream), 0);

  // Reads should advance the stream offset.
  memset(read_buffer, 0xDD, sizeof(read_buffer));
  IREE_EXPECT_OK(iree_io_stream_read(stream, 1, read_buffer, NULL));
  EXPECT_EQ(iree_io_stream_offset(stream), 1);
  EXPECT_EQ(read_buffer[0], 0);
  EXPECT_EQ(read_buffer[1], 0xDD);

  // Read another chunk of 2 bytes.
  memset(read_buffer, 0xDD, sizeof(read_buffer));
  IREE_EXPECT_OK(iree_io_stream_read(stream, 2, read_buffer, NULL));
  EXPECT_EQ(iree_io_stream_offset(stream), 3);
  EXPECT_EQ(read_buffer[0], 1);
  EXPECT_EQ(read_buffer[1], 2);
  EXPECT_EQ(read_buffer[2], 0xDD);

  // Read up to the end of the stream (2 bytes remaining) by reading over.
  memset(read_buffer, 0xDD, sizeof(read_buffer));
  IREE_EXPECT_OK(iree_io_stream_read(stream, 2, read_buffer, NULL));
  EXPECT_EQ(iree_io_stream_offset(stream), iree_io_stream_length(stream));
  EXPECT_TRUE(iree_io_stream_is_eos(stream));
  EXPECT_EQ(read_buffer[0], 3);
  EXPECT_EQ(read_buffer[1], 4);
  EXPECT_EQ(read_buffer[2], 0xDD);

  // Reading from the end of the stream fails with no read length arg.
  memset(read_buffer, 0xDD, sizeof(read_buffer));
  EXPECT_THAT(Status(iree_io_stream_read(stream, sizeof(read_buffer),
                                         read_buffer, NULL)),
              StatusIs(StatusCode::kOutOfRange));

  // Reset back to the origin and try reading off the end.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 0));
  EXPECT_THAT(Status(iree_io_stream_read(stream, sizeof(read_buffer),
                                         read_buffer, NULL)),
              StatusIs(StatusCode::kOutOfRange));
  EXPECT_EQ(iree_io_stream_offset(stream), 0);

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, Write) {
  uint8_t data[5] = {0xDD};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_WRITABLE, iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  const uint8_t write_buffer[8] = {0, 1, 2, 3, 4, 5, 6, 7};

  // Writes of zero length should be a no-op.
  memset(data, 0xDD, sizeof(data));
  IREE_EXPECT_OK(iree_io_stream_write(stream, 0, write_buffer));
  EXPECT_EQ(iree_io_stream_offset(stream), 0);
  EXPECT_EQ(data[0], 0xDD);

  // Writes should advance the stream.
  memset(data, 0xDD, sizeof(data));
  IREE_EXPECT_OK(iree_io_stream_write(stream, 1, write_buffer));
  EXPECT_EQ(iree_io_stream_offset(stream), 1);
  EXPECT_EQ(data[0], 0);
  EXPECT_EQ(data[1], 0xDD);

  // Write 2 more bytes and ensure only those are mutated.
  memset(data, 0xDD, sizeof(data));
  IREE_EXPECT_OK(iree_io_stream_write(stream, 2, &write_buffer[1]));
  EXPECT_EQ(iree_io_stream_offset(stream), 1 + 2);
  EXPECT_EQ(data[0], 0xDD);
  EXPECT_EQ(data[1], 1);
  EXPECT_EQ(data[2], 2);
  EXPECT_EQ(data[3], 0xDD);

  // Writes off the end of the stream should fail.
  EXPECT_THAT(
      Status(iree_io_stream_write(stream, sizeof(write_buffer), write_buffer)),
      StatusIs(StatusCode::kOutOfRange));

  // Seek to the end of the stream and try to write 0 bytes (should be a no-op).
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, 0));
  EXPECT_TRUE(iree_io_stream_is_eos(stream));
  IREE_EXPECT_OK(iree_io_stream_write(stream, 0, write_buffer));
  EXPECT_TRUE(iree_io_stream_is_eos(stream));

  // Writing off the end of the stream should fail.
  EXPECT_THAT(Status(iree_io_stream_write(stream, 1, write_buffer)),
              StatusIs(StatusCode::kOutOfRange));

  // Overwrite the entire contents of the storage.
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 0));
  IREE_EXPECT_OK(iree_io_stream_write(stream, sizeof(data), write_buffer));
  EXPECT_THAT(data,
              ElementsAre(write_buffer[0], write_buffer[1], write_buffer[2],
                          write_buffer[3], write_buffer[4]));

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, Fill) {
  uint8_t data[16] = {0xDD};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_WRITABLE, iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  uint8_t pattern[] = {0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0};

  // Fill patterns must be 1,2,4,8 bytes.
  EXPECT_THAT(Status(iree_io_stream_fill(stream, 1, pattern, 3)),
              StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(Status(iree_io_stream_fill(stream, 1, pattern, 9)),
              StatusIs(StatusCode::kInvalidArgument));

  // Fills are bounds checked.
  EXPECT_THAT(Status(iree_io_stream_fill(stream, 100, pattern, 1)),
              StatusIs(StatusCode::kOutOfRange));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, 0));
  EXPECT_THAT(Status(iree_io_stream_fill(stream, 1, pattern, 1)),
              StatusIs(StatusCode::kOutOfRange));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, -1));
  EXPECT_THAT(Status(iree_io_stream_fill(stream, 2, pattern, 1)),
              StatusIs(StatusCode::kOutOfRange));

  // Fill with pattern size 1.
  memset(data, 0xDD, sizeof(data));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 1));
  IREE_EXPECT_OK(iree_io_stream_fill(stream, 3, pattern, 1));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, -2));
  IREE_EXPECT_OK(iree_io_stream_fill(stream, 2, pattern, 1));
  EXPECT_THAT(data,
              ElementsAre(0xDD, 0x80, 0x80, 0x80, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
                          0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0x80, 0x80));

  // Fill with pattern size 2.
  memset(data, 0xDD, sizeof(data));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 1));
  IREE_EXPECT_OK(iree_io_stream_fill(stream, 3, pattern, 2));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, -4));
  IREE_EXPECT_OK(iree_io_stream_fill(stream, 2, pattern, 2));
  EXPECT_THAT(data,
              ElementsAre(0xDD, 0x80, 0x90, 0x80, 0x90, 0x80, 0x90, 0xDD, 0xDD,
                          0xDD, 0xDD, 0xDD, 0x80, 0x90, 0x80, 0x90));

  // Fill with pattern size 4.
  memset(data, 0xDD, sizeof(data));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 1));
  IREE_EXPECT_OK(iree_io_stream_fill(stream, 2, pattern, 4));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, -4));
  IREE_EXPECT_OK(iree_io_stream_fill(stream, 1, pattern, 4));
  EXPECT_THAT(data,
              ElementsAre(0xDD, 0x80, 0x90, 0xA0, 0xB0, 0x80, 0x90, 0xA0, 0xB0,
                          0xDD, 0xDD, 0xDD, 0x80, 0x90, 0xA0, 0xB0));

  // Fill with pattern size 8.
  memset(data, 0xDD, sizeof(data));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 1));
  IREE_EXPECT_OK(iree_io_stream_fill(stream, 1, pattern, 8));
  EXPECT_THAT(data,
              ElementsAre(0xDD, 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0,
                          0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD));
  memset(data, 0xDD, sizeof(data));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, -8));
  IREE_EXPECT_OK(iree_io_stream_fill(stream, 1, pattern, 8));
  EXPECT_THAT(data,
              ElementsAre(0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0x80,
                          0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0));

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, MapRead) {
  uint8_t data[5] = {0xDD};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE | IREE_IO_STREAM_MODE_MAPPABLE,
      iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  iree_const_byte_span_t span = iree_const_byte_span_empty();

  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 1));
  IREE_EXPECT_OK(iree_io_stream_map_read(stream, 2, &span));
  EXPECT_EQ(span.data, &data[1]);
  EXPECT_EQ(span.data_length, 2);

  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, -1));
  IREE_EXPECT_OK(iree_io_stream_map_read(stream, 1, &span));
  EXPECT_EQ(span.data, &data[4]);
  EXPECT_EQ(span.data_length, 1);

  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 0));
  EXPECT_THAT(Status(iree_io_stream_map_read(stream, 100, &span)),
              StatusIs(StatusCode::kOutOfRange));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, 0));
  EXPECT_THAT(Status(iree_io_stream_map_read(stream, 1, &span)),
              StatusIs(StatusCode::kOutOfRange));

  iree_io_stream_release(stream);
}

TEST(MemoryStreamTest, MapWrite) {
  uint8_t data[5] = {0xDD};
  iree_io_stream_t* stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_WRITABLE | IREE_IO_STREAM_MODE_MAPPABLE,
      iree_make_byte_span(data, sizeof(data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &stream));

  iree_byte_span_t span = iree_byte_span_empty();

  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 1));
  IREE_EXPECT_OK(iree_io_stream_map_write(stream, 2, &span));
  EXPECT_EQ(span.data, &data[1]);
  EXPECT_EQ(span.data_length, 2);

  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, -1));
  IREE_EXPECT_OK(iree_io_stream_map_write(stream, 1, &span));
  EXPECT_EQ(span.data, &data[4]);
  EXPECT_EQ(span.data_length, 1);

  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_SET, 0));
  EXPECT_THAT(Status(iree_io_stream_map_write(stream, 100, &span)),
              StatusIs(StatusCode::kOutOfRange));
  IREE_EXPECT_OK(iree_io_stream_seek(stream, IREE_IO_STREAM_SEEK_FROM_END, 0));
  EXPECT_THAT(Status(iree_io_stream_map_write(stream, 1, &span)),
              StatusIs(StatusCode::kOutOfRange));

  iree_io_stream_release(stream);
}

// Tests copies that fit within a single block.
TEST(MemoryStreamTest, Copy) {
  uint8_t source_data[5] = {0xA0, 0xB0, 0xC0, 0xD0, 0xE0};
  iree_io_stream_t* source_stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE,
      iree_make_byte_span(source_data, sizeof(source_data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &source_stream));

  uint8_t target_data[5] = {0xDD};
  iree_io_stream_t* target_stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_WRITABLE,
      iree_make_byte_span(target_data, sizeof(target_data)),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &target_stream));

  // Bounds checks length.
  EXPECT_THAT(Status(iree_io_stream_copy(source_stream, target_stream, 100)),
              StatusIs(StatusCode::kOutOfRange));

  // Bounds check source.
  IREE_EXPECT_OK(
      iree_io_stream_seek(source_stream, IREE_IO_STREAM_SEEK_FROM_END, 0));
  IREE_EXPECT_OK(
      iree_io_stream_seek(target_stream, IREE_IO_STREAM_SEEK_SET, 0));
  EXPECT_THAT(Status(iree_io_stream_copy(source_stream, target_stream, 1)),
              StatusIs(StatusCode::kOutOfRange));

  // Bounds check target.
  IREE_EXPECT_OK(
      iree_io_stream_seek(source_stream, IREE_IO_STREAM_SEEK_SET, 0));
  IREE_EXPECT_OK(
      iree_io_stream_seek(target_stream, IREE_IO_STREAM_SEEK_FROM_END, 0));
  EXPECT_THAT(Status(iree_io_stream_copy(source_stream, target_stream, 1)),
              StatusIs(StatusCode::kOutOfRange));

  // Copy entire contents.
  memset(target_data, 0xDD, sizeof(target_data));
  IREE_EXPECT_OK(
      iree_io_stream_seek(source_stream, IREE_IO_STREAM_SEEK_SET, 0));
  IREE_EXPECT_OK(
      iree_io_stream_seek(target_stream, IREE_IO_STREAM_SEEK_SET, 0));
  IREE_EXPECT_OK(
      iree_io_stream_copy(source_stream, target_stream, sizeof(target_data)));
  EXPECT_THAT(target_data, ElementsAreArray(source_data));

  // Copy an interior subrange.
  memset(target_data, 0xDD, sizeof(target_data));
  IREE_EXPECT_OK(
      iree_io_stream_seek(source_stream, IREE_IO_STREAM_SEEK_SET, 0));
  IREE_EXPECT_OK(
      iree_io_stream_seek(target_stream, IREE_IO_STREAM_SEEK_SET, 1));
  IREE_EXPECT_OK(iree_io_stream_copy(source_stream, target_stream, 2));
  EXPECT_THAT(target_data, ElementsAre(0xDD, 0xA0, 0xB0, 0xDD, 0xDD));

  // Copy to up to the end.
  memset(target_data, 0xDD, sizeof(target_data));
  IREE_EXPECT_OK(
      iree_io_stream_seek(source_stream, IREE_IO_STREAM_SEEK_SET, 0));
  IREE_EXPECT_OK(
      iree_io_stream_seek(target_stream, IREE_IO_STREAM_SEEK_FROM_END, -2));
  IREE_EXPECT_OK(iree_io_stream_copy(source_stream, target_stream, 2));
  EXPECT_THAT(target_data, ElementsAre(0xDD, 0xDD, 0xDD, 0xA0, 0xB0));

  iree_io_stream_release(source_stream);
  iree_io_stream_release(target_stream);
}

// Tests a copy that should trip into the multi-block code path.
TEST(MemoryStreamTest, CopyLarge) {
  std::vector<uint8_t> source_data(1 * 1024 * 1024);
  for (size_t i = 0; i < source_data.size(); ++i) {
    source_data[i] = (uint8_t)i;
  }
  iree_io_stream_t* source_stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_READABLE,
      iree_make_byte_span(source_data.data(), source_data.size()),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &source_stream));

  std::vector<uint8_t> target_data(1 * 1024 * 1024);
  iree_io_stream_t* target_stream = NULL;
  IREE_ASSERT_OK(iree_io_memory_stream_wrap(
      IREE_IO_STREAM_MODE_WRITABLE,
      iree_make_byte_span(target_data.data(), target_data.size()),
      iree_io_stream_release_callback_null(), iree_allocator_system(),
      &target_stream));

  // Copy an interior subrange.
  memset(target_data.data(), 0xDD, target_data.size());
  IREE_EXPECT_OK(
      iree_io_stream_seek(source_stream, IREE_IO_STREAM_SEEK_SET, 1));
  IREE_EXPECT_OK(
      iree_io_stream_seek(target_stream, IREE_IO_STREAM_SEEK_SET, 2));
  IREE_EXPECT_OK(iree_io_stream_copy(source_stream, target_stream,
                                     source_data.size() - 10));
  for (size_t i = 0; i < target_data.size(); ++i) {
    if (i < 2) {
      // Before the copy range.
      EXPECT_EQ(target_data[i], 0xDD);
    } else if (i < source_data.size() - 10 + 2) {
      // In the copy range.
      EXPECT_EQ(target_data[i], source_data[1 + i - 2]);
    } else {
      // After the copy range.
      EXPECT_EQ(target_data[i], 0xDD);
    }
  }

  iree_io_stream_release(source_stream);
  iree_io_stream_release(target_stream);
}

}  // namespace
