// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2016 XSky <haomai@xsky.com>
 *
 * Author: Haomai Wang <haomaiwang@gmail.com>
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 *
 */

#include <map>
#include <random>
#include <gtest/gtest.h>

#include "msg/async/dpdk/UserspaceEvent.h"
#include "global/global_context.h"

class UserspaceManagerTest : public ::testing::Test {
 public:
  UserspaceEventManager *manager;

  UserspaceManagerTest() {}
  virtual void SetUp() {
    manager = new UserspaceEventManager(g_ceph_context);
  }
  virtual void TearDown() {
    delete manager;
  }
};

TEST_F(UserspaceManagerTest, BasicTest) {
  int events[10];
  int masks[10];
  int fd = manager->get_eventfd();
  ASSERT_EQ(fd, 1);
  ASSERT_EQ(0, manager->listen(fd, 1));
  ASSERT_EQ(0, manager->notify(fd, 1));
  ASSERT_EQ(1, manager->poll(events, masks, 10, nullptr));
  ASSERT_EQ(fd, events[0]);
  ASSERT_EQ(1, masks[0]);
  ASSERT_EQ(0, manager->notify(fd, 2));
  ASSERT_EQ(0, manager->poll(events, masks, 10, nullptr));
  ASSERT_EQ(0, manager->unlisten(fd, 1));
  ASSERT_EQ(0, manager->notify(fd, 1));
  ASSERT_EQ(0, manager->poll(events, masks, 10, nullptr));
  manager->close(fd);
  fd = manager->get_eventfd();
  ASSERT_EQ(fd, 1);
  ASSERT_EQ(0, manager->poll(events, masks, 10, nullptr));
}

TEST_F(UserspaceManagerTest, FailTest) {
  int events[10];
  int masks[10];
  int fd = manager->get_eventfd();
  ASSERT_EQ(fd, 1);
  ASSERT_EQ(-ENOENT, manager->listen(fd+1, 1));
  ASSERT_EQ(-ENOENT, manager->notify(fd+1, 1));
  ASSERT_EQ(0, manager->poll(events, masks, 10, nullptr));
  ASSERT_EQ(-ENOENT, manager->unlisten(fd+1, 1));
  manager->close(fd);
}

TEST_F(UserspaceManagerTest, StressTest) {
  std::vector<std::pair<int, int> > mappings;
  int events[10];
  int masks[10];
  std::random_device rd;
  std::default_random_engine rng(rd());
  std::uniform_int_distribution<> dist(0, 100);

  mappings.resize(1001);
  mappings[0] = std::make_pair(-1, -1);
  for (int i = 0; i < 1000; ++i) {
    int fd = manager->get_eventfd();
    ASSERT_TRUE(fd > 0);
    mappings[fd] = std::make_pair(0, 0);
  }
  int r = 0;
  int fd = manager->get_eventfd();
  auto get_activate_count = [](std::vector<std::pair<int, int> > &m) {
    std::vector<int> fds;
    int mask = 0;
    size_t idx = 0;
    for (auto &&p : m) {
      mask = p.first & p.second;
      if (p.first != -1 && mask) {
        p.second &= (~mask);
        fds.push_back(idx);
        std::cerr << " activate " << idx << " mask " << mask << std::endl;
      }
      ++idx;
    }
    return fds;
  };
  for (int i = 0; i < 10000; ++i) {
    int value = dist(rng);
    fd = dist(rng) % mappings.size();
    auto &p = mappings[fd];
    int mask = dist(rng) % 2 + 1;
    if (value > 55) {
      r = manager->notify(fd, mask);
      if (p.first == -1) {
        ASSERT_EQ(p.second, -1);
        ASSERT_EQ(r, -ENOENT);
      } else {
        p.second |= mask;
        ASSERT_EQ(r, 0);
      }
      std::cerr << " notify fd " << fd << " mask " << mask << " r " << r << std::endl;
    } else if (value > 45) {
      r = manager->listen(fd, mask);
      std::cerr << " listen fd " << fd << " mask " << mask << " r " << r << std::endl;
      if (p.first == -1) {
        ASSERT_EQ(p.second, -1);
        ASSERT_EQ(r, -ENOENT);
      } else {
        p.first |= mask;
        ASSERT_EQ(r, 0);
      }
    } else if (value > 35) {
      r = manager->unlisten(fd, mask);
      std::cerr << " unlisten fd " << fd << " mask " << mask << " r " << r << std::endl;
      if (p.first == -1) {
        ASSERT_EQ(p.second, -1);
        ASSERT_EQ(r, -ENOENT);
      } else {
        p.first &= ~mask;
        ASSERT_EQ(r, 0);
      }
    } else if (value > 20) {
      std::set<int> actual, expected;
      do {
        r = manager->poll(events, masks, 3, nullptr);
        std::cerr << " poll " << r;
        for (int k = 0; k < r; ++k) {
          std::cerr << events[k] << " ";
          actual.insert(events[k]);
        }
      } while (r == 3);
      std::cerr << std::endl;
      auto fds = get_activate_count(mappings);
      for (auto &&d : fds)
        expected.insert(d);
      ASSERT_EQ(expected, actual);
    } else if (value > 10) {
      r = manager->get_eventfd();
      std::cerr << " open fd " << r << std::endl;
      ASSERT_TRUE(r > 0);
      if ((size_t)r >= mappings.size())
        mappings.resize(r+1);
      mappings[r] = std::make_pair(0, 0);
    } else {
      manager->close(fd);
      std::cerr << " close fd " << fd << std::endl;
      mappings[fd] = std::make_pair(-1, -1);
    }
    ASSERT_TRUE(manager->check());
  }
}

/*
 * Local Variables:
 * compile-command: "cd ../.. ; make ceph_test_userspace_event &&
 *    ./ceph_test_userspace_event.cc
 *
 * End:
 */
