text: // Copyright (c) 2011-2016 The Cryptonote developers
  // Copyright (c) 2014-2016 SDN developers
  // Distributed under the MIT/X11 software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #include "TestNetwork.h"
  
  #include <fstream>
  #include <boost/filesystem.hpp>
  
  #include "CryptoNoteConfig.h"
  #include "InProcTestNode.h"
  #include "RPCTestNode.h"
  
  #ifdef _WIN32
  const std::string daemonExec = std::string(cn::CRYPTONOTE_NAME) + "d.exe";
  #else
  const std::string daemonExec = std::string(cn::CRYPTONOTE_NAME) + "d";
  #endif
  
  namespace {
  
  using namespace Tests;
  
  void writeConfiguration(const std::string& confFile, const TestNodeConfiguration& cfg) {
    std::ofstream config(confFile, std::ios_base::trunc | std::ios_base::out);
  
    config
      << "rpc-bind-port=" << cfg.rpcPort << std::endl
      << "p2p-bind-port=" << cfg.p2pPort << std::endl
      << "log-level=4" << std::endl
      << "log-file=" << cfg.logFile << std::endl;
  
    for (const auto& ex : cfg.exclusiveNodes) {
      config << "add-exclusive-node=" << ex << std::endl;
    }
  }
  
  bool waitDaemonReady(TestNode& node) {
    for (size_t i = 0;; ++i) {
      if (node.getLocalHeight() > 0) {
        break;
      } else if (i < 2 * 60) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
      } else {
        return false;
      }
    }
    return true;
  }
  
  void copyBlockchainFiles(bool testnet, const std::string& from, const std::string& to) {
    boost::filesystem::path fromPath(from);
    boost::filesystem::path toPath(to);
  
    auto files = {
      std::make_pair("blockindexes.dat", true),
      std::make_pair("blocks.dat", true),
      std::make_pair("blockscache.dat", false),
      std::make_pair("blockchainindices.dat", false)
    };
  
    for (const auto& item : files) {
      try {
        boost::filesystem::path filePath = std::string(testnet ? "testnet_" : "") + item.first;
        boost::filesystem::copy(fromPath / filePath, toPath / filePath);
      } catch (...) {
        if (item.second) { 
          // if file is required, the rethrow error
          throw;
        }
      }
    }
  }
  
  }
  
  
  namespace Tests {
  
  
  TestNetworkBuilder::TestNetworkBuilder(size_t nodeCount, Topology topology, uint16_t rpcBasePort, uint16_t p2pBasePort) :
    nodeCount(nodeCount), 
    topology(topology), 
    rpcBasePort(rpcBasePort), 
    p2pBasePort(p2pBasePort),
    baseDataDir("."),
    testnet(true)
  {}
  
  std::vector<TestNodeConfiguration> TestNetworkBuilder::build() {
    std::vector<TestNodeConfiguration> cfg;
  
    for (size_t i = 0; i < nodeCount; ++i) {
      cfg.push_back(buildNodeConfiguration(i));
    }
  
    return cfg;
  }
  
  TestNetworkBuilder& TestNetworkBuilder::setDataDirectory(const std::string& dataDir) {
    this->baseDataDir = dataDir;
    return *this;
  }
  
  TestNetworkBuilder& TestNetworkBuilder::setBlockchain(const std::string& blockchainDir) {
    this->blockchainLocation = blockchainDir;
    return *this;
  }
  
  TestNetworkBuilder& TestNetworkBuilder::setTestnet(bool isTestnet) {
    this->testnet = isTestnet;
    return *this;
  }
  
  TestNodeConfiguration TestNetworkBuilder::buildNodeConfiguration(size_t index) {
    TestNodeConfiguration cfg;
  
    if (!baseDataDir.empty()) {
      cfg.dataDir = baseDataDir + "/node" + std::to_string(index);
    }
  
    if (!blockchainLocation.empty()) {
      cfg.blockchainLocation = blockchainLocation;
    }
  
    cfg.daemonPath = daemonExec; // default
    cfg.testnet = testnet;
    cfg.logFile = std::string("test_") + cn::CRYPTONOTE_NAME + "d" + std::to_string(index) + ".log";
  
    uint16_t rpcPort = static_cast<uint16_t>(rpcBasePort + index);
    uint16_t p2pPort = static_cast<uint16_t>(p2pBasePort + index);
  
    cfg.p2pPort = p2pPort;
    cfg.rpcPort = rpcPort;
  
    switch (topology) {
    case Topology::Line:
      if (index != 0) {
        cfg.exclusiveNodes.push_back("127.0.0.1:" + std::to_string(p2pPort - 1));
      }
      break;
  
    case Topology::Ring: {
      uint16_t p2pExternalPort = static_cast<uint16_t>(p2pBasePort + (index + 1) % nodeCount);
      cfg.exclusiveNodes.push_back("127.0.0.1:" + std::to_string(p2pExternalPort));
      break;
    }
  
    case Topology::Star:
      if (index == 0) {
        for (size_t node = 1; node < nodeCount; ++node) {
          cfg.exclusiveNodes.push_back("127.0.0.1:" + std::to_string(p2pBasePort + node));
        }
      }
      break;
    }
  
    return cfg;
  
  }
  
  TestNetwork::TestNetwork(platform_system::Dispatcher& dispatcher, const cn::Currency& currency) : 
    m_dispatcher(dispatcher),
    m_currency(currency) {
  }
  
  void TestNetwork::addNodes(const std::vector<TestNodeConfiguration>& nodes) {
    for (const auto& n : nodes) {
      addNode(n);
    }
  }
  
  void TestNetwork::addNode(const TestNodeConfiguration& cfg) {
    std::unique_ptr<TestNode> node;
  
    boost::system::error_code ec;
    boost::filesystem::remove_all(cfg.dataDir, ec);
    boost::filesystem::create_directory(cfg.dataDir);
  
    if (!cfg.blockchainLocation.empty()) {
      copyBlockchainFiles(cfg.testnet, cfg.blockchainLocation, cfg.dataDir);
    }
  
    switch (cfg.nodeType) {
    case NodeType::InProcess:
      node.reset(new InProcTestNode(cfg, m_currency));
      break;
    case NodeType::RPC:
      node = startDaemon(cfg);
      break;
    }
  
    nodes.push_back(std::make_pair(std::move(node), cfg));
  }
  
  void TestNetwork::waitNodesReady() {
    for (auto& node : nodes) {
      if (!waitDaemonReady(*node.first)) {
        throw std::runtime_error("Daemon startup failure (timeout)");
      }
    }
  }
  
  TestNode& TestNetwork::getNode(size_t index) {
    if (index >= nodes.size()) {
      throw std::runtime_error("Invalid node index");
    }
  
    return *nodes[index].first;
  }
  
  void TestNetwork::shutdown() {
    for (auto& node : nodes) {
      node.first->stopDaemon();
    }
  
    for (auto& daemon : m_daemons) {
      daemon.wait();
    }
  
    std::this_thread::sleep_for(std::chrono::seconds(1));
  
    for (auto& node : nodes) {
      if (node.second.cleanupDataDir) {
        boost::filesystem::remove_all(node.second.dataDir);
      }
    }
  
  }
  
  
  std::unique_ptr<TestNode> TestNetwork::startDaemon(const TestNodeConfiguration& cfg) {
    if (!boost::filesystem::exists(cfg.daemonPath)) {
      throw std::runtime_error("daemon binary wasn't found");
    }
  
    writeConfiguration(cfg.dataDir + "/daemon.conf", cfg);
  
    Process process;
    std::vector<std::string> daemonArgs = { "--data-dir=" + cfg.dataDir, "--config-file=daemon.conf" };
  
    if (cfg.testnet) {
      daemonArgs.emplace_back("--testnet");
    }
  
    process.startChild(cfg.daemonPath, daemonArgs);
  
    std::unique_ptr<TestNode> node(new RPCTestNode(cfg.rpcPort, m_dispatcher));
    m_daemons.push_back(process);
  
    return node;
  }
  
  
  
  
  
  }
  