// Copyright (c) 2012 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 "butil/posix/file_descriptor_shuffle.h"
#include <gtest/gtest.h>

namespace {

// 'Duplicated' file descriptors start at this number
const int kDuplicateBase = 1000;

}  // namespace

namespace butil {

struct Action {
  enum Type {
    CLOSE,
    MOVE,
    DUPLICATE,
  };

  Action(Type in_type, int in_fd1, int in_fd2 = -1)
      : type(in_type),
        fd1(in_fd1),
        fd2(in_fd2) {
  }

  bool operator==(const Action& other) const {
    return other.type == type &&
           other.fd1 == fd1 &&
           other.fd2 == fd2;
  }

  Type type;
  int fd1;
  int fd2;
};

class InjectionTracer : public InjectionDelegate {
 public:
  InjectionTracer()
      : next_duplicate_(kDuplicateBase) {
  }

  virtual bool Duplicate(int* result, int fd) OVERRIDE {
    *result = next_duplicate_++;
    actions_.push_back(Action(Action::DUPLICATE, *result, fd));
    return true;
  }

  virtual bool Move(int src, int dest) OVERRIDE {
    actions_.push_back(Action(Action::MOVE, src, dest));
    return true;
  }

  virtual void Close(int fd) OVERRIDE {
    actions_.push_back(Action(Action::CLOSE, fd));
  }

  const std::vector<Action>& actions() const { return actions_; }

 private:
  int next_duplicate_;
  std::vector<Action> actions_;
};

TEST(FileDescriptorShuffleTest, Empty) {
  InjectiveMultimap map;
  InjectionTracer tracer;

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  EXPECT_EQ(0u, tracer.actions().size());
}

TEST(FileDescriptorShuffleTest, Noop) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 0, false));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  EXPECT_EQ(0u, tracer.actions().size());
}

TEST(FileDescriptorShuffleTest, NoopAndClose) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 0, true));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  EXPECT_EQ(0u, tracer.actions().size());
}

TEST(FileDescriptorShuffleTest, Simple1) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, false));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(1u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
}

TEST(FileDescriptorShuffleTest, Simple2) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, false));
  map.push_back(InjectionArc(2, 3, false));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(2u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 2, 3));
}

TEST(FileDescriptorShuffleTest, Simple3) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, true));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(2u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::CLOSE, 0));
}

TEST(FileDescriptorShuffleTest, Simple4) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(10, 0, true));
  map.push_back(InjectionArc(1, 1, true));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(2u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 10, 0));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::CLOSE, 10));
}

TEST(FileDescriptorShuffleTest, Cycle) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, false));
  map.push_back(InjectionArc(1, 0, false));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(4u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] ==
              Action(Action::DUPLICATE, kDuplicateBase, 1));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1));
  EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0));
  EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase));
}

TEST(FileDescriptorShuffleTest, CycleAndClose1) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, true));
  map.push_back(InjectionArc(1, 0, false));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(4u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] ==
              Action(Action::DUPLICATE, kDuplicateBase, 1));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1));
  EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0));
  EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase));
}

TEST(FileDescriptorShuffleTest, CycleAndClose2) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, false));
  map.push_back(InjectionArc(1, 0, true));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(4u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] ==
              Action(Action::DUPLICATE, kDuplicateBase, 1));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1));
  EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0));
  EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase));
}

TEST(FileDescriptorShuffleTest, CycleAndClose3) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, true));
  map.push_back(InjectionArc(1, 0, true));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(4u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] ==
              Action(Action::DUPLICATE, kDuplicateBase, 1));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1));
  EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0));
  EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase));
}

TEST(FileDescriptorShuffleTest, Fanout) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, false));
  map.push_back(InjectionArc(0, 2, false));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(2u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2));
}

TEST(FileDescriptorShuffleTest, FanoutAndClose1) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, true));
  map.push_back(InjectionArc(0, 2, false));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(3u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2));
  EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0));
}

TEST(FileDescriptorShuffleTest, FanoutAndClose2) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, false));
  map.push_back(InjectionArc(0, 2, true));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(3u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2));
  EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0));
}

TEST(FileDescriptorShuffleTest, FanoutAndClose3) {
  InjectiveMultimap map;
  InjectionTracer tracer;
  map.push_back(InjectionArc(0, 1, true));
  map.push_back(InjectionArc(0, 2, true));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
  ASSERT_EQ(3u, tracer.actions().size());
  EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
  EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2));
  EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0));
}

class FailingDelegate : public InjectionDelegate {
 public:
  virtual bool Duplicate(int* result, int fd) OVERRIDE {
    return false;
  }

  virtual bool Move(int src, int dest) OVERRIDE {
    return false;
  }

  virtual void Close(int fd) OVERRIDE {}
};

TEST(FileDescriptorShuffleTest, EmptyWithFailure) {
  InjectiveMultimap map;
  FailingDelegate failing;

  EXPECT_TRUE(PerformInjectiveMultimap(map, &failing));
}

TEST(FileDescriptorShuffleTest, NoopWithFailure) {
  InjectiveMultimap map;
  FailingDelegate failing;
  map.push_back(InjectionArc(0, 0, false));

  EXPECT_TRUE(PerformInjectiveMultimap(map, &failing));
}

TEST(FileDescriptorShuffleTest, Simple1WithFailure) {
  InjectiveMultimap map;
  FailingDelegate failing;
  map.push_back(InjectionArc(0, 1, false));

  EXPECT_FALSE(PerformInjectiveMultimap(map, &failing));
}

}  // namespace butil
