0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 12:12:15 +02:00
openvpn3/test/unittests/test_helper.hpp
James Yonan 159a64e338
unittests: work around some limitations in googletest
Signed-off-by: James Yonan <james@openvpn.net>
2020-08-19 18:52:54 +02:00

314 lines
7.7 KiB
C++

// 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__); \
}