system_tests.cpp raw

   1  // Copyright (c) 2019-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  //
   5  
   6  #include <bitcoin-build-config.h> // IWYU pragma: keep
   7  
   8  #include <common/run_command.h>
   9  #include <test/util/common.h>
  10  #include <test/util/setup_common.h>
  11  #include <univalue.h>
  12  #include <util/string.h>
  13  
  14  #include <cstdlib>
  15  #include <iostream>
  16  #include <string_view>
  17  
  18  #ifdef ENABLE_EXTERNAL_SIGNER
  19  #include <util/subprocess.h>
  20  #endif // ENABLE_EXTERNAL_SIGNER
  21  
  22  #include <boost/test/unit_test.hpp>
  23  
  24  #include <string>
  25  
  26  namespace {
  27  // When set in the environment, test_bitcoin acts as a mock subprocess for the
  28  // run_command test below instead of running unit tests.
  29  constexpr const char* MOCK_PROCESS_ENV = "BITCOIN_TEST_MOCK_PROCESS";
  30  
  31  const bool g_maybe_run_mock_dispatcher_before_main{[]() {
  32      const char* name = std::getenv(MOCK_PROCESS_ENV);
  33      if (!name) return false;
  34      const std::string_view n{name};
  35      if (n == "valid_json") {
  36          std::cout << R"({"success": true})" << std::endl;
  37          std::_Exit(EXIT_SUCCESS);
  38      }
  39      if (n == "nonzeroexit_nooutput") {
  40          std::_Exit(EXIT_FAILURE);
  41      }
  42      if (n == "nonzeroexit_stderroutput") {
  43          std::cerr << "err" << std::endl;
  44          std::_Exit(EXIT_FAILURE);
  45      }
  46      if (n == "invalid_json") {
  47          std::cout << "{" << std::endl;
  48          std::_Exit(EXIT_SUCCESS);
  49      }
  50      if (n == "pass_stdin_to_stdout") {
  51          std::string s;
  52          std::getline(std::cin, s);
  53          std::cout << s << std::endl;
  54          std::_Exit(EXIT_SUCCESS);
  55      }
  56      std::cerr << "Unknown mock process: " << n << std::endl;
  57      std::_Exit(EXIT_FAILURE);
  58  }()};
  59  } // namespace
  60  
  61  BOOST_FIXTURE_TEST_SUITE(system_tests, BasicTestingSetup)
  62  
  63  #ifdef ENABLE_EXTERNAL_SIGNER
  64  
  65  static std::vector<std::string> mock_executable(const std::string& name)
  66  {
  67  #if defined(WIN32)
  68      _putenv_s(MOCK_PROCESS_ENV, name.c_str());
  69  #else
  70      setenv(MOCK_PROCESS_ENV, name.c_str(), /*overwrite=*/1);
  71  #endif
  72      return {boost::unit_test::framework::master_test_suite().argv[0]};
  73  }
  74  
  75  BOOST_AUTO_TEST_CASE(run_command)
  76  {
  77      {
  78          const UniValue result = RunCommandParseJSON({});
  79          BOOST_CHECK(result.isNull());
  80      }
  81      {
  82          const UniValue result = RunCommandParseJSON(mock_executable("valid_json"));
  83          BOOST_CHECK(result.isObject());
  84          const UniValue& success = result.find_value("success");
  85          BOOST_CHECK(!success.isNull());
  86          BOOST_CHECK_EQUAL(success.get_bool(), true);
  87      }
  88      {
  89          // An invalid command is handled by cpp-subprocess
  90  #ifdef WIN32
  91          const std::string expected{"CreateProcess failed: "};
  92  #else
  93          const std::string expected{"execve failed: "};
  94  #endif
  95          BOOST_CHECK_EXCEPTION(RunCommandParseJSON({"invalid_command"}), subprocess::CalledProcessError, HasReason(expected));
  96      }
  97      {
  98          // Return non-zero exit code, no output to stderr
  99          const std::vector<std::string> command = mock_executable("nonzeroexit_nooutput");
 100          BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) {
 101              const std::string what{e.what()};
 102              BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned %d: \n", util::Join(command, " "), EXIT_FAILURE)) != std::string::npos);
 103              return true;
 104          });
 105      }
 106      {
 107          // Return non-zero exit code, with error message for stderr
 108          const std::vector<std::string> command = mock_executable("nonzeroexit_stderroutput");
 109          const std::string expected{"err"};
 110          BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) {
 111              const std::string what(e.what());
 112              BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned %s: %s", util::Join(command, " "), EXIT_FAILURE, "err")) != std::string::npos);
 113              BOOST_CHECK(what.find(expected) != std::string::npos);
 114              return true;
 115          });
 116      }
 117      {
 118          // Unable to parse JSON
 119          BOOST_CHECK_EXCEPTION(RunCommandParseJSON(mock_executable("invalid_json")), std::runtime_error, HasReason("Unable to parse JSON: {"));
 120      }
 121      {
 122          // Test stdin
 123          const UniValue result = RunCommandParseJSON(mock_executable("pass_stdin_to_stdout"), "{\"success\": true}");
 124          BOOST_CHECK(result.isObject());
 125          const UniValue& success = result.find_value("success");
 126          BOOST_CHECK(!success.isNull());
 127          BOOST_CHECK_EQUAL(success.get_bool(), true);
 128      }
 129  }
 130  #endif // ENABLE_EXTERNAL_SIGNER
 131  
 132  BOOST_AUTO_TEST_SUITE_END()
 133