// Copyright 2019 The Chromium 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 <algorithm>
#include <list>
#include <memory>
#include <string>

#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/string_piece.h"
#include "base/test/task_environment.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/data_pipe_producer.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "mojo/public/cpp/system/string_data_source.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace mojo {
namespace {

// Test helper. Reads a consumer handle, accumulating data into a string. Reads
// until encountering an error (e.g. peer closure), at which point it invokes an
// async callback.
class DataPipeReader {
 public:
  explicit DataPipeReader(ScopedDataPipeConsumerHandle consumer_handle,
                          base::OnceClosure on_read_done)
      : consumer_handle_(std::move(consumer_handle)),
        on_read_done_(std::move(on_read_done)),
        watcher_(FROM_HERE,
                 SimpleWatcher::ArmingPolicy::AUTOMATIC,
                 base::SequencedTaskRunnerHandle::Get()) {
    watcher_.Watch(consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
                   MOJO_WATCH_CONDITION_SATISFIED,
                   base::BindRepeating(&DataPipeReader::OnDataAvailable,
                                       base::Unretained(this)));
  }

  DataPipeReader(const DataPipeReader&) = delete;
  DataPipeReader& operator=(const DataPipeReader&) = delete;

  ~DataPipeReader() = default;

  const std::string& data() const { return data_; }

 private:
  void OnDataAvailable(MojoResult result, const HandleSignalsState& state) {
    if (result == MOJO_RESULT_OK) {
      uint32_t size = 64;
      std::vector<char> buffer(size, 0);
      MojoResult read_result;
      do {
        read_result = consumer_handle_->ReadData(buffer.data(), &size,
                                                 MOJO_READ_DATA_FLAG_NONE);
        if (read_result == MOJO_RESULT_OK) {
          std::copy(buffer.begin(), buffer.begin() + size,
                    std::back_inserter(data_));
        }
      } while (read_result == MOJO_RESULT_OK);

      if (read_result == MOJO_RESULT_SHOULD_WAIT)
        return;
    }

    if (result != MOJO_RESULT_CANCELLED)
      watcher_.Cancel();

    std::move(on_read_done_).Run();
  }

  ScopedDataPipeConsumerHandle consumer_handle_;
  base::OnceClosure on_read_done_;
  SimpleWatcher watcher_;
  std::string data_;
};

class StringDataSourceTest : public testing::Test {
 public:
  StringDataSourceTest() = default;

  StringDataSourceTest(const StringDataSourceTest&) = delete;
  StringDataSourceTest& operator=(const StringDataSourceTest&) = delete;

  ~StringDataSourceTest() override = default;

 protected:
  static void CreateDataPipe(uint32_t capacity,
                             mojo::ScopedDataPipeProducerHandle* producer,
                             mojo::ScopedDataPipeConsumerHandle* consumer) {
    MojoCreateDataPipeOptions options;
    options.struct_size = sizeof(MojoCreateDataPipeOptions);
    options.element_num_bytes = 1;
    options.capacity_num_bytes = capacity;
    ASSERT_EQ(MOJO_RESULT_OK,
              mojo::CreateDataPipe(&options, *producer, *consumer));
  }

  static void WriteStringThenCloseProducer(
      std::unique_ptr<DataPipeProducer> producer,
      const base::StringPiece& str,
      StringDataSource::AsyncWritingMode mode) {
    DataPipeProducer* raw_producer = producer.get();
    raw_producer->Write(
        std::make_unique<mojo::StringDataSource>(str, mode),
        base::BindOnce([](std::unique_ptr<DataPipeProducer> producer,
                          MojoResult result) {},
                       std::move(producer)));
  }

  static void WriteStringsThenCloseProducer(
      std::unique_ptr<DataPipeProducer> producer,
      std::list<base::StringPiece> strings,
      StringDataSource::AsyncWritingMode mode) {
    DataPipeProducer* raw_producer = producer.get();
    base::StringPiece str = strings.front();
    strings.pop_front();
    raw_producer->Write(
        std::make_unique<mojo::StringDataSource>(str, mode),
        base::BindOnce(
            [](std::unique_ptr<DataPipeProducer> producer,
               std::list<base::StringPiece> strings,
               StringDataSource::AsyncWritingMode mode, MojoResult result) {
              if (!strings.empty())
                WriteStringsThenCloseProducer(std::move(producer),
                                              std::move(strings), mode);
            },
            std::move(producer), std::move(strings), mode));
  }

 private:
  base::test::TaskEnvironment task_environment_;
};

TEST_F(StringDataSourceTest, EqualCapacity) {
  const std::string kTestString = "Hello, world!";

  base::RunLoop loop;
  mojo::ScopedDataPipeProducerHandle producer_handle;
  mojo::ScopedDataPipeConsumerHandle consumer_handle;
  CreateDataPipe(static_cast<uint32_t>(kTestString.size()), &producer_handle,
                 &consumer_handle);
  DataPipeReader reader(std::move(consumer_handle), loop.QuitClosure());
  WriteStringThenCloseProducer(
      std::make_unique<DataPipeProducer>(std::move(producer_handle)),
      kTestString,
      StringDataSource::AsyncWritingMode::
          STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION);
  loop.Run();

  EXPECT_EQ(kTestString, reader.data());
}

TEST_F(StringDataSourceTest, UnderCapacity) {
  const std::string kTestString = "Hello, world!";

  base::RunLoop loop;
  mojo::ScopedDataPipeProducerHandle producer_handle;
  mojo::ScopedDataPipeConsumerHandle consumer_handle;
  CreateDataPipe(static_cast<uint32_t>(kTestString.size() * 2),
                 &producer_handle, &consumer_handle);
  DataPipeReader reader(std::move(consumer_handle), loop.QuitClosure());
  WriteStringThenCloseProducer(
      std::make_unique<DataPipeProducer>(std::move(producer_handle)),
      kTestString,
      StringDataSource::AsyncWritingMode::
          STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION);
  loop.Run();

  EXPECT_EQ(kTestString, reader.data());
}

TEST_F(StringDataSourceTest, OverCapacity) {
  const std::string kTestString = "Hello, world!";

  base::RunLoop loop;
  mojo::ScopedDataPipeProducerHandle producer_handle;
  mojo::ScopedDataPipeConsumerHandle consumer_handle;
  CreateDataPipe(static_cast<uint32_t>(kTestString.size() / 2),
                 &producer_handle, &consumer_handle);
  DataPipeReader reader(std::move(consumer_handle), loop.QuitClosure());
  WriteStringThenCloseProducer(
      std::make_unique<DataPipeProducer>(std::move(producer_handle)),
      kTestString,
      StringDataSource::AsyncWritingMode::STRING_STAYS_VALID_UNTIL_COMPLETION);
  loop.Run();

  EXPECT_EQ(kTestString, reader.data());
}

TEST_F(StringDataSourceTest, TinyPipe) {
  const std::string kTestString = "Hello, world!";

  base::RunLoop loop;
  mojo::ScopedDataPipeProducerHandle producer_handle;
  mojo::ScopedDataPipeConsumerHandle consumer_handle;
  CreateDataPipe(1u, &producer_handle, &consumer_handle);
  DataPipeReader reader(std::move(consumer_handle), loop.QuitClosure());
  WriteStringThenCloseProducer(
      std::make_unique<DataPipeProducer>(std::move(producer_handle)),
      kTestString,
      StringDataSource::AsyncWritingMode::
          STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION);
  loop.Run();

  EXPECT_EQ(kTestString, reader.data());
}

TEST_F(StringDataSourceTest, MultipleWrites) {
  const std::string kTestString1 = "Hello, world!";
  const std::string kTestString2 = "There is a lot of data coming your way!";
  const std::string kTestString3 = "So many strings!";
  const std::string kTestString4 = "Your cup runneth over!";

  base::RunLoop loop;
  mojo::ScopedDataPipeProducerHandle producer_handle;
  mojo::ScopedDataPipeConsumerHandle consumer_handle;
  CreateDataPipe(4u, &producer_handle, &consumer_handle);
  DataPipeReader reader(std::move(consumer_handle), loop.QuitClosure());
  WriteStringsThenCloseProducer(
      std::make_unique<DataPipeProducer>(std::move(producer_handle)),
      {kTestString1, kTestString2, kTestString3, kTestString4},
      StringDataSource::AsyncWritingMode::
          STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION);
  loop.Run();

  EXPECT_EQ(kTestString1 + kTestString2 + kTestString3 + kTestString4,
            reader.data());
}

}  // namespace
}  // namespace mojo
