Newer
Older
XinYang_IOS / Carthage / Checkouts / OpenVPNAdapter / Sources / OpenVPN3 / test / unittests / test_helper.hpp
@zhangfeng zhangfeng on 7 Dec 2023 7 KB 1.8.0
//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2020 OpenVPN Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License Version 3
//    as published by the Free Software Foundation.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.

#pragma once

#include <openvpn/log/logbase.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/hexstr.hpp>
#include <openvpn/random/mtrandapi.hpp>

#include <iostream>
#include <gtest/gtest.h>
#include <fstream>
#include <mutex>

namespace openvpn {
  class LogOutputCollector : public LogBase
  {
  public:
    LogOutputCollector() : log_context(this)
    {
    }

    void log(const std::string& l) override
    {
      std::lock_guard<std::mutex> lock(mutex);

      if (output_log)
	std::cout << l;
      if (collect_log)
	out << l;
    }

    /**
     * Return the collected log out
     * @return the log output as string
     */
    std::string getOutput() const
    {
      return out.str();
    }

    /**
     * Allow to access the underlying output stream to direct
     * output from function that want to write to a stream to it
     * @return that will be captured by this log
     */
    std::ostream& getStream()
    {
      return out;
    }

    /**
     * Changes if the logging to stdout should be done
     * @param doOutput
     */
    void setPrintOutput(bool doOutput)
    {
      output_log = doOutput;
    }

    /**
     * Return current state of stdout output
     * @return current state of output
     */
    bool isStdoutEnabled() const
    {
      return output_log;
    }

    /**
     * Starts collecting log output. This will also
     * disable stdout output and clear the collected output if there is any
     */
    void startCollecting()
    {
      collect_log = true;
      output_log = false;
      // Reset our buffer
      out.str(std::string());
      out.clear();
    }

    /**
     * Stops collecting log output. Will reenable stdout output.
     * @return the output collected
     */
    std::string stopCollecting()
    {
      collect_log = false;
      output_log = true;
      return getOutput();
    }

  private:
    bool output_log = true;
    bool collect_log = false;
    std::stringstream out;
    std::mutex mutex{};
    Log::Context log_context;
  };
}

extern openvpn::LogOutputCollector* testLog;

/**
 * Overrides stdout during the run of a function. Primarly for silencing
 * log function that throw an exception when something is wrong
 * @param doLogOutput Use stdout while running
 * @param test_func function to run
 */
inline void override_logOutput(bool doLogOutput, void (* test_func)())
{
  bool previousOutputState = testLog->isStdoutEnabled();
  testLog->setPrintOutput(doLogOutput);
  test_func();
  testLog->setPrintOutput(previousOutputState);
}

/**
 * Reads the file with the expected output and returns it as a
 * string. This function delibrately does not include the
 * ASSERT_EQ call since otherwise gtest will report a the
 * assert failure in this file rather than in the right place
 * @param filename
 * @return
 */
inline std::string getExpectedOutput(const std::string& filename)
{
  auto fullpath = UNITTEST_SOURCE_DIR "/output/" + filename;
  std::ifstream f(fullpath);
  if (!f.good())
    {
      throw std::runtime_error("Error opening file " + fullpath);
    }
  std::string expected_output((std::istreambuf_iterator<char>(f)),
			      std::istreambuf_iterator<char>());
  return expected_output;
}

#ifdef WIN32
#include <windows.h>

inline std::string getTempDirPath(const std::string& fn)
{
  char buf [MAX_PATH];

  EXPECT_NE(GetTempPathA(MAX_PATH, buf), 0);
  return std::string(buf) + fn;
}
#else

inline std::string getTempDirPath(const std::string& fn)
{
  return "/tmp/" + fn;
}

#endif

/**
 * Returns a sorted string join with the delimiter
 * This function modifes the input
 * @param r the array to join
 * @param delim the delimiter to use
 * @return A string joined by delim from the sorted vector r
 */
template<class T>
inline std::string getSortedJoinedString(std::vector<T>& r, const std::string& delim = "|")
{
  std::sort(r.begin(), r.end());
  std::stringstream s;
  std::copy(r.begin(), r.end(), std::ostream_iterator<std::string>(s, delim.c_str()));
  return s.str();
}

namespace detail {
  class line
  {
    std::string data;
  public:
    friend std::istream& operator>>(std::istream& is, line& l)
    {
      std::getline(is, l.data);
      return is;
    }

    operator std::string() const
    { return data; }
  };
}

/**
 * Splits a string into lines and returns them in a sorted output string
 */
inline std::string getSortedString(const std::string& output)
{
  std::stringstream ss{output};

  std::istream_iterator<detail::line> begin{ss};
  std::istream_iterator<detail::line> end;
  std::vector<std::string> lines {begin, end};

  // sort lines
  std::sort(lines.begin(), lines.end());

  // join strings with \n
  std::stringstream s;
  std::copy(lines.begin(), lines.end(), std::ostream_iterator<std::string>(s, "\n"));
  return s.str();
}

/**
 * Predictable RNG that claims to be secure to be used in reproducable unit
 * tests
 */
class FakeSecureRand : public openvpn::RandomAPI
{
public:
  FakeSecureRand(const unsigned char initial=0)
    : next(initial)
  {
  }

  virtual std::string name() const override
  {
    return "FakeRNG";
  }

  virtual bool is_crypto() const override
  {
    return true;
  }

  virtual void rand_bytes(unsigned char *buf, size_t size) override
  {
    rand_bytes_(buf, size);
    //OPENVPN_LOG("RAND: " << openvpn::render_hex(buf, size));
  }

  virtual bool rand_bytes_noexcept(unsigned char *buf, size_t size) override
  {
    rand_bytes(buf, size);
    return true;
  }

private:
  // fake RNG -- just use an incrementing sequence
  void rand_bytes_(unsigned char *buf, size_t size)
  {
    while (size--)
      *buf++ = next++;
  }

  unsigned char next;
};

// googletest is missing the ability to test for specific
// text inside a thrown exception, so we implement it here

#define JY_EXPECT_THROW(statement, expected_exception, expected_text) \
try { \
    statement; \
    OPENVPN_THROW_EXCEPTION("JY_EXPECT_THROW: no exception was thrown " << __FILE__ << ':' << __LINE__); \
} \
catch (const expected_exception& e) \
{ \
  if (std::string(e.what()).find(expected_text) == std::string::npos) \
    OPENVPN_THROW_EXCEPTION("JY_EXPECT_THROW: did not find expected text in exception at " << __FILE__ << ':' << __LINE__); \
}

// googletest ASSERT macros can't be used inside constructors
// or non-void-returning functions, so implement workaround here

#define JY_ASSERT_TRUE(value) \
{ \
  if (!(value)) \
    OPENVPN_THROW_EXCEPTION("JY_ASSERT_TRUE: failure at " << __FILE__ << ':' << __LINE__); \
}

#define JY_ASSERT_FALSE(value) \
{ \
  if (value) \
    OPENVPN_THROW_EXCEPTION("JY_ASSERT_FALSE: failure at " << __FILE__ << ':' << __LINE__); \
}

#define JY_ASSERT_EQ(v1, v2) \
{ \
  if ((v1) != (v2)) \
    OPENVPN_THROW_EXCEPTION("JY_ASSERT_EQ: failure at " << __FILE__ << ':' << __LINE__); \
}

#define JY_ASSERT_NE(v1, v2) \
{ \
  if ((v1) == (v2)) \
    OPENVPN_THROW_EXCEPTION("JY_ASSERT_NE: failure at " << __FILE__ << ':' << __LINE__); \
}