// Copyright 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <brillo/streams/stream.h>

#include <limits>

#include <base/callback.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <brillo/bind_lambda.h>
#include <brillo/message_loops/fake_message_loop.h>
#include <brillo/streams/stream_errors.h>

using testing::DoAll;
using testing::InSequence;
using testing::Return;
using testing::SaveArg;
using testing::SetArgPointee;
using testing::_;

namespace brillo {

using AccessMode = Stream::AccessMode;
using Whence = Stream::Whence;

// To verify "non-trivial" methods implemented in Stream, mock out the
// "trivial" methods to make sure the ones we are interested in testing
// actually end up calling the expected methods with right parameters.
class MockStreamImpl : public Stream {
 public:
  MockStreamImpl() = default;

  MOCK_CONST_METHOD0(IsOpen, bool());
  MOCK_CONST_METHOD0(CanRead, bool());
  MOCK_CONST_METHOD0(CanWrite, bool());
  MOCK_CONST_METHOD0(CanSeek, bool());
  MOCK_CONST_METHOD0(CanGetSize, bool());

  MOCK_CONST_METHOD0(GetSize, uint64_t());
  MOCK_METHOD2(SetSizeBlocking, bool(uint64_t, ErrorPtr*));
  MOCK_CONST_METHOD0(GetRemainingSize, uint64_t());

  MOCK_CONST_METHOD0(GetPosition, uint64_t());
  MOCK_METHOD4(Seek, bool(int64_t, Whence, uint64_t*, ErrorPtr*));

  // Omitted: ReadAsync
  // Omitted: ReadAllAsync
  MOCK_METHOD5(ReadNonBlocking, bool(void*, size_t, size_t*, bool*, ErrorPtr*));
  // Omitted: ReadBlocking
  // Omitted: ReadAllBlocking

  // Omitted: WriteAsync
  // Omitted: WriteAllAsync
  MOCK_METHOD4(WriteNonBlocking, bool(const void*, size_t, size_t*, ErrorPtr*));
  // Omitted: WriteBlocking
  // Omitted: WriteAllBlocking

  MOCK_METHOD1(FlushBlocking, bool(ErrorPtr*));
  MOCK_METHOD1(CloseBlocking, bool(ErrorPtr*));

  MOCK_METHOD3(WaitForData, bool(AccessMode,
                                 const base::Callback<void(AccessMode)>&,
                                 ErrorPtr*));
  MOCK_METHOD4(WaitForDataBlocking,
               bool(AccessMode, base::TimeDelta, AccessMode*, ErrorPtr*));

 private:
  DISALLOW_COPY_AND_ASSIGN(MockStreamImpl);
};

TEST(Stream, TruncateBlocking) {
  MockStreamImpl stream_mock;
  EXPECT_CALL(stream_mock, GetPosition()).WillOnce(Return(123));
  EXPECT_CALL(stream_mock, SetSizeBlocking(123, _)).WillOnce(Return(true));
  EXPECT_TRUE(stream_mock.TruncateBlocking(nullptr));
}

TEST(Stream, SetPosition) {
  MockStreamImpl stream_mock;
  EXPECT_CALL(stream_mock, Seek(12345, Whence::FROM_BEGIN, _, _))
      .WillOnce(Return(true));
  EXPECT_TRUE(stream_mock.SetPosition(12345, nullptr));

  // Test too large an offset (that doesn't fit in signed 64 bit value).
  ErrorPtr error;
  uint64_t max_offset = std::numeric_limits<int64_t>::max();
  EXPECT_CALL(stream_mock, Seek(max_offset, _, _, _))
      .WillOnce(Return(true));
  EXPECT_TRUE(stream_mock.SetPosition(max_offset, nullptr));

  EXPECT_FALSE(stream_mock.SetPosition(max_offset + 1, &error));
  EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
  EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode());
}

TEST(Stream, ReadAsync) {
  size_t read_size = 0;
  bool succeeded = false;
  bool failed = false;
  auto success_callback = [&read_size, &succeeded](size_t size) {
    read_size = size;
    succeeded = true;
  };
  auto error_callback = [&failed](const Error* /* error */) { failed = true; };

  MockStreamImpl stream_mock;
  base::Callback<void(AccessMode)> data_callback;
  char buf[10];

  // This sets up an initial non blocking read that would block, so ReadAsync()
  // should wait for more data.
  EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
      .WillOnce(
          DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true)));
  EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _))
      .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
  EXPECT_TRUE(stream_mock.ReadAsync(buf, sizeof(buf),
                                    base::Bind(success_callback),
                                    base::Bind(error_callback), nullptr));
  EXPECT_EQ(0u, read_size);
  EXPECT_FALSE(succeeded);
  EXPECT_FALSE(failed);

  // Since the previous call is waiting for the data to be available, we can't
  // schedule another read.
  ErrorPtr error;
  EXPECT_FALSE(stream_mock.ReadAsync(buf, sizeof(buf),
                                     base::Bind(success_callback),
                                     base::Bind(error_callback), &error));
  EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
  EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode());
  EXPECT_EQ("Another asynchronous operation is still pending",
            error->GetMessage());

  // Making the data available via data_callback should not schedule the
  // success callback from the main loop and run it directly instead.
  EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(7),
                      SetArgPointee<3>(false),
                      Return(true)));
  data_callback.Run(AccessMode::READ);
  EXPECT_EQ(7u, read_size);
  EXPECT_FALSE(failed);
}

TEST(Stream, ReadAsync_DontWaitForData) {
  bool succeeded = false;
  bool failed = false;
  auto success_callback = [&succeeded](size_t /* size */) { succeeded = true; };
  auto error_callback = [&failed](const Error* /* error */) { failed = true; };

  MockStreamImpl stream_mock;
  char buf[10];
  FakeMessageLoop fake_loop_{nullptr};
  fake_loop_.SetAsCurrent();

  EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
      .WillOnce(
          DoAll(SetArgPointee<2>(5), SetArgPointee<3>(false), Return(true)));
  EXPECT_CALL(stream_mock, WaitForData(_, _, _)).Times(0);
  EXPECT_TRUE(stream_mock.ReadAsync(buf, sizeof(buf),
                                    base::Bind(success_callback),
                                    base::Bind(error_callback), nullptr));
  // Even if ReadNonBlocking() returned some data without waiting, the
  // |success_callback| should not run yet.
  EXPECT_TRUE(fake_loop_.PendingTasks());
  EXPECT_FALSE(succeeded);
  EXPECT_FALSE(failed);

  // Since the previous callback is still waiting in the main loop, we can't
  // schedule another read yet.
  ErrorPtr error;
  EXPECT_FALSE(stream_mock.ReadAsync(buf, sizeof(buf),
                                     base::Bind(success_callback),
                                     base::Bind(error_callback), &error));
  EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
  EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode());
  EXPECT_EQ("Another asynchronous operation is still pending",
            error->GetMessage());

  fake_loop_.Run();
  EXPECT_TRUE(succeeded);
  EXPECT_FALSE(failed);
}

TEST(Stream, ReadAllAsync) {
  bool succeeded = false;
  bool failed = false;
  auto success_callback = [&succeeded]() { succeeded = true; };
  auto error_callback = [&failed](const Error* /* error */) { failed = true; };

  MockStreamImpl stream_mock;
  base::Callback<void(AccessMode)> data_callback;
  char buf[10];

  // This sets up an initial non blocking read that would block, so
  // ReadAllAsync() should wait for more data.
  EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
      .WillOnce(
          DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true)));
  EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _))
      .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
  EXPECT_TRUE(stream_mock.ReadAllAsync(buf, sizeof(buf),
                                       base::Bind(success_callback),
                                       base::Bind(error_callback),
                                       nullptr));
  EXPECT_FALSE(succeeded);
  EXPECT_FALSE(failed);
  testing::Mock::VerifyAndClearExpectations(&stream_mock);

  // ReadAllAsync() will try to read non blocking until the read would block
  // before it waits for the data to be available again.
  EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(7),
                      SetArgPointee<3>(false),
                      Return(true)));
  EXPECT_CALL(stream_mock, ReadNonBlocking(buf + 7, 3, _, _, _))
      .WillOnce(
          DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true)));
  EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _))
      .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
  data_callback.Run(AccessMode::READ);
  EXPECT_FALSE(succeeded);
  EXPECT_FALSE(failed);
  testing::Mock::VerifyAndClearExpectations(&stream_mock);

  EXPECT_CALL(stream_mock, ReadNonBlocking(buf + 7, 3, _, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(3),
                      SetArgPointee<3>(true),
                      Return(true)));
  data_callback.Run(AccessMode::READ);
  EXPECT_TRUE(succeeded);
  EXPECT_FALSE(failed);
}

TEST(Stream, ReadAllAsync_EOS) {
  bool succeeded = false;
  bool failed = false;
  auto success_callback = [&succeeded]() { succeeded = true; };
  auto error_callback = [&failed](const Error* error) {
    ASSERT_EQ(errors::stream::kDomain, error->GetDomain());
    ASSERT_EQ(errors::stream::kPartialData, error->GetCode());
    failed = true;
  };

  MockStreamImpl stream_mock;
  base::Callback<void(AccessMode)> data_callback;
  char buf[10];

  EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
      .WillOnce(
          DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true)));
  EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _))
      .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
  EXPECT_TRUE(stream_mock.ReadAllAsync(buf, sizeof(buf),
                                       base::Bind(success_callback),
                                       base::Bind(error_callback),
                                       nullptr));

  // ReadAsyncAll() should finish and fail once ReadNonBlocking() returns an
  // end-of-stream condition.
  EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(7),
                      SetArgPointee<3>(true),
                      Return(true)));
  data_callback.Run(AccessMode::READ);
  EXPECT_FALSE(succeeded);
  EXPECT_TRUE(failed);
}

TEST(Stream, ReadBlocking) {
  MockStreamImpl stream_mock;
  char buf[1024];
  size_t read = 0;

  EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(24),
                      SetArgPointee<3>(false),
                      Return(true)));
  EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr));
  EXPECT_EQ(24, read);

  EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(0),
                      SetArgPointee<3>(true),
                      Return(true)));
  EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr));
  EXPECT_EQ(0, read);

  {
    InSequence seq;
    EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
        .WillOnce(DoAll(SetArgPointee<2>(0),
                        SetArgPointee<3>(false),
                        Return(true)));
    EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _))
        .WillOnce(Return(true));
    EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
        .WillOnce(DoAll(SetArgPointee<2>(0),
                        SetArgPointee<3>(false),
                        Return(true)));
    EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _))
        .WillOnce(Return(true));
    EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
        .WillOnce(DoAll(SetArgPointee<2>(124),
                        SetArgPointee<3>(false),
                        Return(true)));
  }
  EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr));
  EXPECT_EQ(124, read);

  {
    InSequence seq;
    EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
        .WillOnce(DoAll(SetArgPointee<2>(0),
                        SetArgPointee<3>(false),
                        Return(true)));
    EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _))
        .WillOnce(Return(false));
  }
  EXPECT_FALSE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr));
}

TEST(Stream, ReadAllBlocking) {
  class MockReadBlocking : public MockStreamImpl {
   public:
    MOCK_METHOD4(ReadBlocking, bool(void*, size_t, size_t*, ErrorPtr*));
  } stream_mock;

  char buf[1024];

  EXPECT_CALL(stream_mock, ReadBlocking(buf, 1024, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(24), Return(true)));
  EXPECT_CALL(stream_mock, ReadBlocking(buf + 24, 1000, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(1000), Return(true)));
  EXPECT_TRUE(stream_mock.ReadAllBlocking(buf, sizeof(buf), nullptr));

  ErrorPtr error;
  EXPECT_CALL(stream_mock, ReadBlocking(buf, 1024, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(24), Return(true)));
  EXPECT_CALL(stream_mock, ReadBlocking(buf + 24, 1000, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
  EXPECT_FALSE(stream_mock.ReadAllBlocking(buf, sizeof(buf), &error));
  EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
  EXPECT_EQ(errors::stream::kPartialData, error->GetCode());
}

TEST(Stream, WriteAsync) {
  size_t write_size = 0;
  bool failed = false;
  auto success_callback = [&write_size](size_t size) { write_size = size; };
  auto error_callback = [&failed](const Error* /* error */) { failed = true; };

  MockStreamImpl stream_mock;
  InSequence s;
  base::Callback<void(AccessMode)> data_callback;
  char buf[10] = {};

  // WriteNonBlocking returns a blocking situation (size_written = 0) so the
  // WaitForData() is run.
  EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
  EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _))
      .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
  EXPECT_TRUE(stream_mock.WriteAsync(buf, sizeof(buf),
                                     base::Bind(success_callback),
                                     base::Bind(error_callback), nullptr));
  EXPECT_EQ(0u, write_size);
  EXPECT_FALSE(failed);

  ErrorPtr error;
  EXPECT_FALSE(stream_mock.WriteAsync(buf, sizeof(buf),
                                      base::Bind(success_callback),
                                      base::Bind(error_callback), &error));
  EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
  EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode());
  EXPECT_EQ("Another asynchronous operation is still pending",
            error->GetMessage());

  EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(7), Return(true)));
  data_callback.Run(AccessMode::WRITE);
  EXPECT_EQ(7u, write_size);
  EXPECT_FALSE(failed);
}

TEST(Stream, WriteAllAsync) {
  bool succeeded = false;
  bool failed = false;
  auto success_callback = [&succeeded]() { succeeded = true; };
  auto error_callback = [&failed](const Error* /* error */) { failed = true; };

  MockStreamImpl stream_mock;
  base::Callback<void(AccessMode)> data_callback;
  char buf[10] = {};

  EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
  EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _))
      .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
  EXPECT_TRUE(stream_mock.WriteAllAsync(buf, sizeof(buf),
                                        base::Bind(success_callback),
                                        base::Bind(error_callback),
                                        nullptr));
  testing::Mock::VerifyAndClearExpectations(&stream_mock);
  EXPECT_FALSE(succeeded);
  EXPECT_FALSE(failed);

  EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(7), Return(true)));
  EXPECT_CALL(stream_mock, WriteNonBlocking(buf + 7, 3, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
  EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _))
      .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
  data_callback.Run(AccessMode::WRITE);
  testing::Mock::VerifyAndClearExpectations(&stream_mock);
  EXPECT_FALSE(succeeded);
  EXPECT_FALSE(failed);

  EXPECT_CALL(stream_mock, WriteNonBlocking(buf + 7, 3, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(3), Return(true)));
  data_callback.Run(AccessMode::WRITE);
  EXPECT_TRUE(succeeded);
  EXPECT_FALSE(failed);
}

TEST(Stream, WriteBlocking) {
  MockStreamImpl stream_mock;
  char buf[1024];
  size_t written = 0;

  EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(24), Return(true)));
  EXPECT_TRUE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr));
  EXPECT_EQ(24, written);

  {
    InSequence seq;
    EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _))
          .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
    EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _))
        .WillOnce(Return(true));
    EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _))
          .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
    EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _))
        .WillOnce(Return(true));
    EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _))
          .WillOnce(DoAll(SetArgPointee<2>(124), Return(true)));
  }
  EXPECT_TRUE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr));
  EXPECT_EQ(124, written);

  {
    InSequence seq;
    EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _))
          .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
    EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _))
        .WillOnce(Return(false));
  }
  EXPECT_FALSE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr));
}

TEST(Stream, WriteAllBlocking) {
  class MockWritelocking : public MockStreamImpl {
   public:
    MOCK_METHOD4(WriteBlocking, bool(const void*, size_t, size_t*, ErrorPtr*));
  } stream_mock;

  char buf[1024];

  EXPECT_CALL(stream_mock, WriteBlocking(buf, 1024, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(24), Return(true)));
  EXPECT_CALL(stream_mock, WriteBlocking(buf + 24, 1000, _, _))
      .WillOnce(DoAll(SetArgPointee<2>(1000), Return(true)));
  EXPECT_TRUE(stream_mock.WriteAllBlocking(buf, sizeof(buf), nullptr));
}

}  // namespace brillo