Commit 64b257c0 authored by Martin Marinov's avatar Martin Marinov Committed by GitHub Enterprise
Browse files

MTBR-739 Refactor shareable code for the Test Environment (#17)

* Move reusable code from Test::Environment in ReForm
* Add System::RedirectStream
* Add missing include for dup2 on MacOS and Linux for RedirectStream
* Add Test::Checksum::Time
* Add Test::Paths
* Clarify and simplify the convention for the input and output paths
parent d6a833c3
......@@ -4,9 +4,12 @@ set(my_headers
${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumDebugEvent.hh
${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumFile.hh
${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumIssueNumber.hh
${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumLevel.hh
${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumNumberT.hh
${CMAKE_CURRENT_SOURCE_DIR}/types.hh
${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumTime.hh
${CMAKE_CURRENT_SOURCE_DIR}/TestPaths.hh
${CMAKE_CURRENT_SOURCE_DIR}/TestResult.hh
${CMAKE_CURRENT_SOURCE_DIR}/types.hh
PARENT_SCOPE
)
......@@ -17,5 +20,6 @@ set(my_sources
${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumDebugEvent.cc
${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumFile.cc
${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumIssueNumber.cc
${CMAKE_CURRENT_SOURCE_DIR}/TestPaths.cc
PARENT_SCOPE
)
......@@ -13,7 +13,8 @@ namespace Checksum {
Level run_lvl = L_NONE;
namespace {
namespace
{
Registry& registry_modify()
{
......
......@@ -11,6 +11,7 @@
#else
#include <Base/Test/TestResult.hh>
#include <Base/Test/TestChecksumLevel.hh>
#include <Base/Utils/OStringStream.hh>
#include <map>
#include <sstream>
......@@ -19,16 +20,7 @@ namespace Test
{
namespace Checksum
{
//! Enumerate the checksum levels
enum Level
{
L_NONE,
L_STABLE,
L_PRIME,
L_ALL
};
extern Level run_lvl; //<! The checksum run level
extern Level run_lvl; //<! The global checksum run level
const char* const LEVEL_TEXT[4] = {"NONE", "STABLE", "PRIME", "ALL"};
//! typedef String, this is used a lot in this namespace
......@@ -168,7 +160,7 @@ protected:
virtual Difference compare_data(const String& _old, const String& _new) const;
protected:
const Level lvl_;
const Level lvl_; // the checksum level
private:
const char* const name_;
......
// (C) Copyright 2020 by Autodesk, Inc.
#ifndef BASE_TESTCHECKSUMLEVEL_HH_INCLUDED
#define BASE_TESTCHECKSUMLEVEL_HH_INCLUDED
#ifdef TEST_ON
namespace Test
{
namespace Checksum
{
//! Enumerate the checksum level, higher levels include lower ones
enum Level
{
L_NONE, //! No checksums are enabled
L_STABLE, //! Stable checksums, useful for cross-platform testing
L_PRIME, //! Checksums that exist in build configurations w/o DEB_ON
L_ALL //! Checksums that exist only in DEB_ON build configurations
};
} // namespace Checksum
} // namespace Test
#endif // TEST_ON
#endif // REFORM_TESTCHECKSUMLEVEL_HH_INCLUDED
// (C) Copyright 2020 by Autodesk, Inc.
#ifndef BASE_TESTCHECKSUMTIME_HH_INCLUDED
#define BASE_TESTCHECKSUMTIME_HH_INCLUDED
#ifdef TEST_ON
#include <Base/Test/TestChecksumNumberT.hh>
#include <chrono>
namespace Test
{
namespace Checksum
{
class Time : public NumberT<double>
{
public:
using Number = NumberT<double>;
public:
Time(const char* const _name = "time", const Level _lvl = L_ALL)
: Number(_name, _lvl)
{
}
void start() { start_time_ = std::chrono::system_clock::now(); }
void record()
{
std::chrono::time_point<std::chrono::system_clock> end_time;
std::chrono::duration<double> time_diff_sec =
std::chrono::system_clock::now() - start_time_;
Number::record(Result::OK, time_diff_sec.count());
}
private:
std::chrono::time_point<std::chrono::system_clock> start_time_;
};
} // namespace Checksum
} // namespace Test
#endif // TEST_ON
#endif // BASE_TESTCHECKSUMTIME_HH_INCLUDED
// Copyright 2020 Autodesk, Inc. All rights reserved.
#ifdef TEST_ON
#include "TestPaths.hh"
#include "TestChecksumNumberT.hh"
#ifdef __APPLE__
#include <boost/filesystem.hpp>
#else
#include <filesystem>
#endif
#include <iostream>
#include <string>
// TODO: convert this to a debug option once we implement it
//#define TEST_WAIT_FOR_DEBUG_ATTACH
#ifdef TEST_WAIT_FOR_DEBUG_ATTACH
#include <thread>
#endif //TEST_WAIT_FOR_DEBUG_ATTACH
namespace Test
{
#ifdef __APPLE__
namespace fs = boost::filesystem;
#else // __APPLE__
namespace fs = std::filesystem;
#endif // __APPLE__
// Similar to BASE_THROW[_if] but the errors are not expected to be converted in
// error codes, so we just throw strings. Throwing exceptions through DLL
// boundaries is not a great idea in general, but in this case it is OK as we
// just want the test executable to fail immediately.
// TODO: Should we move this stuff somewhere more visible?
#define TEST_THROW(EXPR) \
{ \
Base::OStringStream strm; \
strm << EXPR; \
throw std::runtime_error(strm.str); \
}
#define TEST_THROW_if(COND, EXPR) if (COND) TEST_THROW(EXPR)
Base::IOutputStream& operator<<(Base::IOutputStream& _os, const fs::path& _path)
{
return _os << _path.string();
}
namespace
{
void make_directory(const fs::path& _dir)
{
fs::path chk_dir;
for (auto path_it = _dir.begin(); path_it != _dir.end(); ++path_it)
{
chk_dir /= *path_it;
if (!fs::exists(chk_dir) && !fs::create_directories(chk_dir))
{ // Directory creation can fail if the directory has already been created
// by another process. This is likely because multiple test processes are
// scheduled all together. In this case we can avoid a failure.
// TODO: Make this even more secure, e.g., try this check a few more times
// (5-10?), sleep the process before each attempt by 0.1sec.
TEST_THROW_if(!fs::exists(chk_dir),
"Failed creating directory "
<< chk_dir << " in the requested directory path " << _dir);
}
}
}
}//namespace
Paths::Paths(
const int _argc, const char* const* _argv, const char* const _out_root)
{
// allow to attached the debugger a test process executed by the test system
#ifdef TEST_WAIT_FOR_DEBUG_ATTACH
if (boost::filesystem::exists("c:/wait_to_debug_test.yes"))
{ // placing a breakpoint on the cycle below allows us to exit the sleep
std::chrono::seconds duration(10);
for (int i = 0; i < 100; ++i)
std::this_thread::sleep_for(duration);
}
#endif // TEST_WAIT_FOR_DEBUG_ATTACH
TEST_THROW_if(_argc < 2, "Test arguments are not supplied");
// parse the extract the input test folder & name
fs::path in_path(_argv[1]);
if (fs::is_directory(in_path))
{// we expect that the first argument is the test root directory
TEST_THROW_if(_argc < 3, // is the subpath argument provided?
"Subpath argument for test root folder " << in_path << " missing");
fs::path subpath(_argv[2]);
in_path /= subpath;
if (_out_root != nullptr)
{
const auto out_dir = fs::path(_out_root) / fs::path(subpath);
make_directory(out_dir);
fs::current_path(out_dir); // set output as current path
}
}
// else: special case to run a test from the IDE as a single path
TEST_THROW_if(!fs::is_regular_file(in_path), in_path << " is not a file");
in_path_ = in_path.string();
in_dir_ = in_path.parent_path().string();
filename_ = in_path.filename().string();
}
std::string Paths::input_filename_path(const char* const _flnm) const
{
fs::path inpt_flnm_path(input_directory());
inpt_flnm_path /= _flnm;
return inpt_flnm_path.string();
}
} // namespace Test
#endif//TEST_ON
// Copyright 2020 Autodesk, Inc. All rights reserved.
#ifndef BASE_TESTENVIRONMENT_HH_INCLUDED
#define BASE_TESTENVIRONMENT_HH_INCLUDED
#ifdef TEST_ON
#include <string>
namespace Test
{
//! Reusable setup code to process the test executable arguments.
class Paths
{
public:
/*!
Set up the test environment from the test app command-line interface (CLI) and
the system environment.
We expect the following CLI argument format:
> test-app input_root_directory input_relative_file_path [test arguments]
If an output root directory is supplied, a test-specific output directory is
created (including any parent directories) by appending the supplied output
root folder with the relative file. We then switch to this output folder.
To facilitate debugging a test within an IDE, we also support the format:
> test-app input_test_path [test arguments]
In this case the paths data is setup identically, but no test-specific output
folder is created, and the test output is stored within the current folder.
*/
Paths(const int _argc, //!< number of executable arguments
const char* const* _argv, //!< executable arguments
const char* const _out_root = nullptr //!< output root folder
);
//! The input test path (directory + filename)
const std::string& input_path() const { return in_path_; }
//! The input test directory (path - filename)
const std::string& input_directory() const { return in_dir_; }
//! The input test filename (path - directory)
const std::string& filename() const { return filename_; }
/*!
Construct an input filename path from the input directory and the supplied
filename.
*/
std::string input_filename_path(const char* const _flnm) const;
private:
std::string in_path_;
std::string in_dir_;
std::string filename_;
};
} // namespace Test
#endif // TEST_ON
#endif // BASE_TESTENVIRONMENT_HH_INCLUDED
......@@ -5,6 +5,7 @@ set(my_headers
${CMAKE_CURRENT_SOURCE_DIR}/FileOutput.hh
${CMAKE_CURRENT_SOURCE_DIR}/IOutputStream.hh
${CMAKE_CURRENT_SOURCE_DIR}/OStringStream.hh
${CMAKE_CURRENT_SOURCE_DIR}/RedirectStream.hh
${CMAKE_CURRENT_SOURCE_DIR}/StopWatch.hh
${CMAKE_CURRENT_SOURCE_DIR}/Thread.hh
${CMAKE_CURRENT_SOURCE_DIR}/ThrowError.hh
......@@ -17,6 +18,7 @@ set(my_sources
${CMAKE_CURRENT_SOURCE_DIR}/FileOutput.cc
${CMAKE_CURRENT_SOURCE_DIR}/IOutputStream.cc
${CMAKE_CURRENT_SOURCE_DIR}/OStringStream.cc
${CMAKE_CURRENT_SOURCE_DIR}/RedirectStream.cc
${CMAKE_CURRENT_SOURCE_DIR}/StopWatch.cc
PARENT_SCOPE
)
// (C) Copyright 2019 by Autodesk, Inc.
#include "Base/Security/Mandatory.hh"
#include "RedirectStream.hh"
#ifdef WIN32
#include <io.h>
#define dup2 _dup2
#define fileno _fileno
#else // WIN32
#include <unistd.h>
#endif // WIN32
namespace System
{
bool RedirectStream::redirect(FILE* _from_stream)
{
if (_from_stream == nullptr || flnm_ == nullptr || redirected())
return false;
#ifdef WIN32
if (freopen_s(&to_stream_, flnm_, "w", _from_stream) != 0)
to_stream_ = nullptr;
#else
to_stream_ = freopen(flnm_, "w", _from_stream);
#endif
if (to_stream_ == nullptr) // failed?
return false;
from_stream_[0] = _from_stream;
return true;
}
bool RedirectStream::duplicate(FILE* _from_stream)
{
// check we are in the right state to duplicate
if (_from_stream == nullptr || !redirected() || duplicated())
return false;
if (dup2(fileno(from_stream_[0]), fileno(_from_stream)) != 0)
return false; // failed to duplicate
from_stream_[1] = _from_stream;
return true;
}
} // namespace System
// (C) Copyright 2020 by Autodesk, Inc.
#ifndef BASE_REDIRECTSTREAM_HH_INCLUDED
#define BASE_REDIRECTSTREAM_HH_INCLUDED
#include <stdio.h>
namespace System
{
/*!
Redirect up to 2 existing opened streams (usually stderr and/or stdout) in a new
file with the provided filename
*/
class RedirectStream
{
public:
// Default constructor
RedirectStream() {}
// Constructor, sets the name of the redirect stream
RedirectStream(const char* const _flnm) : flnm_(_flnm) {}
// Destructor, closes the redirect stream
~RedirectStream()
{
if (to_stream_ != nullptr)
fclose(to_stream_);
}
//! Set the stream filename (if not set already)
void set_filename(const char* const _flnm)
{
if (!redirected()) // redirected already?
flnm_ = _flnm;
}
//! Redirect _from_stream to a newly created stream with the filename
bool redirect(FILE* _from_stream);
//! Check if a stream has been already redirected
bool redirected() const { return from_stream_[0] != nullptr; }
//! Duplicate another stream to the redirect stream , call that redirect()
bool duplicate(FILE* _from_stream);
//! Check if a stream has been already duplicated
bool duplicated() const { return from_stream_[1] != nullptr; }
protected:
const char* flnm_ = nullptr;
FILE* to_stream_ = nullptr;
FILE* from_stream_[2] = { nullptr, nullptr };
};
} // namespace System
#endif // BASE_REDIRECTSTREAM_HH_INCLUDED
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment