1173 lines
33 KiB
C++
1173 lines
33 KiB
C++
/*
|
|
* Copyright (c) 2009 University of Washington
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "test.h"
|
|
|
|
#include "abort.h"
|
|
#include "assert.h"
|
|
#include "config.h"
|
|
#include "des-metrics.h"
|
|
#include "log.h"
|
|
#include "singleton.h"
|
|
#include "system-path.h"
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <list>
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
/**
|
|
* \file
|
|
* \ingroup testing
|
|
* \brief ns3::TestCase, ns3::TestSuite, ns3::TestRunner implementations,
|
|
*/
|
|
|
|
namespace ns3
|
|
{
|
|
|
|
NS_LOG_COMPONENT_DEFINE("Test");
|
|
|
|
bool
|
|
TestDoubleIsEqual(const double x1, const double x2, const double epsilon)
|
|
{
|
|
NS_LOG_FUNCTION(x1 << x2 << epsilon);
|
|
int exponent;
|
|
double delta;
|
|
double difference;
|
|
|
|
//
|
|
// Find exponent of largest absolute value
|
|
//
|
|
{
|
|
double max = (std::fabs(x1) > std::fabs(x2)) ? x1 : x2;
|
|
std::frexp(max, &exponent);
|
|
}
|
|
|
|
//
|
|
// Form a neighborhood of size 2 * delta
|
|
//
|
|
delta = std::ldexp(epsilon, exponent);
|
|
difference = x1 - x2;
|
|
|
|
return difference <= delta && difference >= -delta;
|
|
}
|
|
|
|
/**
|
|
* \ingroup testingimpl
|
|
* Container for details of a test failure.
|
|
*/
|
|
struct TestCaseFailure
|
|
{
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* \param [in] _cond The name of the condition being tested.
|
|
* \param [in] _actual The actual value returned by the test.
|
|
* \param [in] _limit The expected value.
|
|
* \param [in] _message The associated message.
|
|
* \param [in] _file The source file.
|
|
* \param [in] _line The source line.
|
|
*/
|
|
TestCaseFailure(std::string _cond,
|
|
std::string _actual,
|
|
std::string _limit,
|
|
std::string _message,
|
|
std::string _file,
|
|
int32_t _line);
|
|
std::string cond; /**< The name of the condition being tested. */
|
|
std::string actual; /**< The actual value returned by the test. */
|
|
std::string limit; /**< The expected value. */
|
|
std::string message; /**< The associated message. */
|
|
std::string file; /**< The source file. */
|
|
int32_t line; /**< The source line. */
|
|
};
|
|
|
|
/**
|
|
* Output streamer for TestCaseFailure.
|
|
*
|
|
* \param [in,out] os The output stream.
|
|
* \param [in] failure The TestCaseFailure to print.
|
|
* \returns The stream.
|
|
*/
|
|
std::ostream&
|
|
operator<<(std::ostream& os, const TestCaseFailure& failure)
|
|
{
|
|
os << " test=\"" << failure.cond << "\" actual=\"" << failure.actual << "\" limit=\""
|
|
<< failure.limit << "\" in=\"" << failure.file << ":" << failure.line << "\" "
|
|
<< failure.message;
|
|
|
|
return os;
|
|
}
|
|
|
|
/**
|
|
* \ingroup testingimpl
|
|
* Container for results from a TestCase.
|
|
*/
|
|
struct TestCase::Result
|
|
{
|
|
/** Constructor. */
|
|
Result();
|
|
|
|
/** Test running time. */
|
|
SystemWallClockMs clock;
|
|
/** TestCaseFailure records for each child. */
|
|
std::vector<TestCaseFailure> failure;
|
|
/** \c true if any child TestCases failed. */
|
|
bool childrenFailed;
|
|
};
|
|
|
|
/**
|
|
* \ingroup testingimpl
|
|
* Container for all tests.
|
|
* \todo Move TestRunnerImpl to separate file.
|
|
*/
|
|
class TestRunnerImpl : public Singleton<TestRunnerImpl>
|
|
{
|
|
public:
|
|
/** Constructor. */
|
|
TestRunnerImpl();
|
|
|
|
/**
|
|
* Add a new top-level TestSuite.
|
|
* \param [in] testSuite The new TestSuite.
|
|
*/
|
|
void AddTestSuite(TestSuite* testSuite);
|
|
/** \copydoc TestCase::MustAssertOnFailure() */
|
|
bool MustAssertOnFailure() const;
|
|
/** \copydoc TestCase::MustContinueOnFailure() */
|
|
bool MustContinueOnFailure() const;
|
|
/**
|
|
* Check if this run should update the reference data.
|
|
* \return \c true if we should update the reference data.
|
|
*/
|
|
bool MustUpdateData() const;
|
|
/**
|
|
* Get the path to the root of the source tree.
|
|
*
|
|
* The root directory is defined by the presence of two files:
|
|
* "VERSION" and "LICENSE".
|
|
*
|
|
* \returns The path to the root.
|
|
*/
|
|
std::string GetTopLevelSourceDir() const;
|
|
/**
|
|
* Get the path to temporary directory.
|
|
* \return The temporary directory path.
|
|
*/
|
|
std::string GetTempDir() const;
|
|
/** \copydoc TestRunner::Run() */
|
|
int Run(int argc, char* argv[]);
|
|
|
|
private:
|
|
/**
|
|
* Check if this is the root of the source tree.
|
|
* \param [in] path The path to test.
|
|
* \returns \c true if \pname{path} is the root.
|
|
*/
|
|
bool IsTopLevelSourceDir(std::string path) const;
|
|
/**
|
|
* Clean up characters not allowed in XML.
|
|
*
|
|
* XML files have restrictions on certain characters that may be present in
|
|
* data. We need to replace these characters with their alternate
|
|
* representation on the way into the XML file.
|
|
*
|
|
* Specifically, we make these replacements:
|
|
* Raw Source | Replacement
|
|
* :--------: | :---------:
|
|
* '<' | "<"
|
|
* '>' | ">"
|
|
* '&' | "&"
|
|
* '"' | "&39;"
|
|
* '\' | """
|
|
*
|
|
* \param [in] xml The raw string.
|
|
* \returns The sanitized string.
|
|
*/
|
|
std::string ReplaceXmlSpecialCharacters(std::string xml) const;
|
|
/**
|
|
* Print the test report.
|
|
*
|
|
* \param [in] test The TestCase to print.
|
|
* \param [in,out] os The output stream.
|
|
* \param [in] xml Generate XML output if \c true.
|
|
* \param [in] level Indentation level.
|
|
*/
|
|
void PrintReport(TestCase* test, std::ostream* os, bool xml, int level);
|
|
/**
|
|
* Print the list of all requested test suites.
|
|
*
|
|
* \param [in] begin Iterator to the first TestCase to print.
|
|
* \param [in] end Iterator to the end of the list.
|
|
* \param [in] printTestType Prepend the test type label if \c true.
|
|
*/
|
|
void PrintTestNameList(std::list<TestCase*>::const_iterator begin,
|
|
std::list<TestCase*>::const_iterator end,
|
|
bool printTestType) const;
|
|
/** Print the list of test types. */
|
|
void PrintTestTypeList() const;
|
|
/**
|
|
* Print the help text.
|
|
* \param [in] programName The name of the invoking program.
|
|
*/
|
|
void PrintHelp(const char* programName) const;
|
|
/**
|
|
* Generate the list of tests matching the constraints.
|
|
*
|
|
* Test name and type constraints are or'ed. The duration constraint
|
|
* is and'ed.
|
|
*
|
|
* \param [in] testName Include a specific test by name.
|
|
* \param [in] testType Include all tests of give type.
|
|
* \param [in] maximumTestDuration Restrict to tests shorter than this.
|
|
* \returns The list of tests matching the filter constraints.
|
|
*/
|
|
std::list<TestCase*> FilterTests(std::string testName,
|
|
TestSuite::Type testType,
|
|
TestCase::Duration maximumTestDuration);
|
|
|
|
/** Container type for the test. */
|
|
typedef std::vector<TestSuite*> TestSuiteVector;
|
|
|
|
TestSuiteVector m_suites; //!< The list of tests.
|
|
std::string m_tempDir; //!< The temporary directory.
|
|
bool m_verbose; //!< Produce verbose output.
|
|
bool m_assertOnFailure; //!< \c true if we should assert on failure.
|
|
bool m_continueOnFailure; //!< \c true if we should continue on failure.
|
|
bool m_updateData; //!< \c true if we should update reference data.
|
|
};
|
|
|
|
TestCaseFailure::TestCaseFailure(std::string _cond,
|
|
std::string _actual,
|
|
std::string _limit,
|
|
std::string _message,
|
|
std::string _file,
|
|
int32_t _line)
|
|
: cond(_cond),
|
|
actual(_actual),
|
|
limit(_limit),
|
|
message(_message),
|
|
file(_file),
|
|
line(_line)
|
|
{
|
|
NS_LOG_FUNCTION(this << _cond << _actual << _limit << _message << _file << _line);
|
|
}
|
|
|
|
TestCase::Result::Result()
|
|
: childrenFailed(false)
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
}
|
|
|
|
TestCase::TestCase(std::string name)
|
|
: m_parent(nullptr),
|
|
m_dataDir(""),
|
|
m_runner(nullptr),
|
|
m_result(nullptr),
|
|
m_name(name),
|
|
m_duration(TestCase::Duration::QUICK)
|
|
{
|
|
NS_LOG_FUNCTION(this << name);
|
|
}
|
|
|
|
TestCase::~TestCase()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
NS_ASSERT(m_runner == nullptr);
|
|
m_parent = nullptr;
|
|
delete m_result;
|
|
for (auto i = m_children.begin(); i != m_children.end(); ++i)
|
|
{
|
|
delete *i;
|
|
}
|
|
m_children.clear();
|
|
}
|
|
|
|
void
|
|
TestCase::AddTestCase(TestCase* testCase, TestCase::Duration duration)
|
|
{
|
|
NS_LOG_FUNCTION(&testCase << duration);
|
|
|
|
// Test names are used to create temporary directories,
|
|
// so we test for illegal characters.
|
|
//
|
|
// Windows: <>:"/\|?*
|
|
// http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx
|
|
// Mac: : (deprecated, was path separator in Mac OS Classic, pre X)
|
|
// Unix: / (and .. may give trouble?)
|
|
//
|
|
// The Windows list is too restrictive: we like to label
|
|
// tests with "val = v1 * v2" or "v1 < 3" or "case: foo --> bar"
|
|
// So we allow ':<>*"
|
|
|
|
std::string badchars = "\"/\\|?";
|
|
// Badchar Class Regex Count of failing test names
|
|
// All ":<>\"/\\|?*" 611
|
|
// Allow ':' "<>\"/\\|?*" 128
|
|
// Allow ':<>' "\"/\\|?*" 12
|
|
// Allow ':<>*' "\"/\\|?" 0
|
|
|
|
std::string::size_type badch = testCase->m_name.find_first_of(badchars);
|
|
if (badch != std::string::npos)
|
|
{
|
|
/*
|
|
To count the bad test names, use NS_LOG_UNCOND instead
|
|
of NS_FATAL_ERROR, and the command
|
|
$ ./ns3 run "test-runner --list" 2>&1 | grep "^Invalid" | wc
|
|
*/
|
|
NS_LOG_UNCOND("Invalid test name: cannot contain any of '" << badchars
|
|
<< "': " << testCase->m_name);
|
|
}
|
|
|
|
testCase->m_duration = duration;
|
|
testCase->m_parent = this;
|
|
m_children.push_back(testCase);
|
|
}
|
|
|
|
bool
|
|
TestCase::IsFailed() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_result->childrenFailed || !m_result->failure.empty();
|
|
}
|
|
|
|
void
|
|
TestCase::Run(TestRunnerImpl* runner)
|
|
{
|
|
NS_LOG_FUNCTION(this << runner);
|
|
m_result = new Result();
|
|
m_runner = runner;
|
|
Config::Reset();
|
|
DoSetup();
|
|
m_result->clock.Start();
|
|
for (auto i = m_children.begin(); i != m_children.end(); ++i)
|
|
{
|
|
TestCase* test = *i;
|
|
test->Run(runner);
|
|
if (IsFailed())
|
|
{
|
|
goto out;
|
|
}
|
|
}
|
|
DoRun();
|
|
out:
|
|
m_result->clock.End();
|
|
DoTeardown();
|
|
Config::Reset();
|
|
m_runner = nullptr;
|
|
}
|
|
|
|
std::string
|
|
TestCase::GetName() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_name;
|
|
}
|
|
|
|
TestCase*
|
|
TestCase::GetParent() const
|
|
{
|
|
return m_parent;
|
|
}
|
|
|
|
void
|
|
TestCase::ReportTestFailure(std::string cond,
|
|
std::string actual,
|
|
std::string limit,
|
|
std::string message,
|
|
std::string file,
|
|
int32_t line)
|
|
{
|
|
NS_LOG_FUNCTION(this << cond << actual << limit << message << file << line);
|
|
m_result->failure.emplace_back(cond, actual, limit, message, file, line);
|
|
// set childrenFailed flag on parents.
|
|
TestCase* current = m_parent;
|
|
while (current != nullptr)
|
|
{
|
|
current->m_result->childrenFailed = true;
|
|
current = current->m_parent;
|
|
}
|
|
}
|
|
|
|
bool
|
|
TestCase::MustAssertOnFailure() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_runner->MustAssertOnFailure();
|
|
}
|
|
|
|
bool
|
|
TestCase::MustContinueOnFailure() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_runner->MustContinueOnFailure();
|
|
}
|
|
|
|
std::string
|
|
TestCase::CreateDataDirFilename(std::string filename)
|
|
{
|
|
NS_LOG_FUNCTION(this << filename);
|
|
const TestCase* current = this;
|
|
while (current != nullptr && current->m_dataDir.empty())
|
|
{
|
|
current = current->m_parent;
|
|
}
|
|
if (current == nullptr)
|
|
{
|
|
NS_FATAL_ERROR("No one called SetDataDir prior to calling this function");
|
|
}
|
|
|
|
std::string a = SystemPath::Append(m_runner->GetTopLevelSourceDir(), current->m_dataDir);
|
|
std::string b = SystemPath::Append(a, filename);
|
|
return b;
|
|
}
|
|
|
|
std::string
|
|
TestCase::CreateTempDirFilename(std::string filename)
|
|
{
|
|
NS_LOG_FUNCTION(this << filename);
|
|
if (m_runner->MustUpdateData())
|
|
{
|
|
return CreateDataDirFilename(filename);
|
|
}
|
|
else
|
|
{
|
|
std::list<std::string> names;
|
|
const TestCase* current = this;
|
|
while (current != nullptr)
|
|
{
|
|
names.push_front(current->m_name);
|
|
current = current->m_parent;
|
|
}
|
|
std::string tempDir = SystemPath::Append(m_runner->GetTempDir(),
|
|
SystemPath::Join(names.begin(), names.end()));
|
|
tempDir = SystemPath::CreateValidSystemPath(tempDir);
|
|
|
|
SystemPath::MakeDirectories(tempDir);
|
|
return SystemPath::Append(tempDir, filename);
|
|
}
|
|
}
|
|
|
|
bool
|
|
TestCase::IsStatusFailure() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return !IsStatusSuccess();
|
|
}
|
|
|
|
bool
|
|
TestCase::IsStatusSuccess() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_result->failure.empty();
|
|
}
|
|
|
|
void
|
|
TestCase::SetDataDir(std::string directory)
|
|
{
|
|
NS_LOG_FUNCTION(this << directory);
|
|
m_dataDir = directory;
|
|
}
|
|
|
|
void
|
|
TestCase::DoSetup()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
}
|
|
|
|
void
|
|
TestCase::DoTeardown()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
}
|
|
|
|
TestSuite::TestSuite(std::string name, TestSuite::Type type)
|
|
: TestCase(name),
|
|
m_type(type)
|
|
{
|
|
NS_LOG_FUNCTION(this << name << type);
|
|
TestRunnerImpl::Get()->AddTestSuite(this);
|
|
}
|
|
|
|
TestSuite::Type
|
|
TestSuite::GetTestType()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_type;
|
|
}
|
|
|
|
void
|
|
TestSuite::DoRun()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
}
|
|
|
|
TestRunnerImpl::TestRunnerImpl()
|
|
: m_tempDir(""),
|
|
m_assertOnFailure(false),
|
|
m_continueOnFailure(true),
|
|
m_updateData(false)
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
}
|
|
|
|
void
|
|
TestRunnerImpl::AddTestSuite(TestSuite* testSuite)
|
|
{
|
|
NS_LOG_FUNCTION(this << testSuite);
|
|
m_suites.push_back(testSuite);
|
|
}
|
|
|
|
bool
|
|
TestRunnerImpl::MustAssertOnFailure() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_assertOnFailure;
|
|
}
|
|
|
|
bool
|
|
TestRunnerImpl::MustContinueOnFailure() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_continueOnFailure;
|
|
}
|
|
|
|
bool
|
|
TestRunnerImpl::MustUpdateData() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_updateData;
|
|
}
|
|
|
|
std::string
|
|
TestRunnerImpl::GetTempDir() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_tempDir;
|
|
}
|
|
|
|
bool
|
|
TestRunnerImpl::IsTopLevelSourceDir(std::string path) const
|
|
{
|
|
NS_LOG_FUNCTION(this << path);
|
|
bool haveVersion = false;
|
|
bool haveLicense = false;
|
|
|
|
//
|
|
// If there's a file named VERSION and a file named LICENSE in this
|
|
// directory, we assume it's our top level source directory.
|
|
//
|
|
|
|
std::list<std::string> files = SystemPath::ReadFiles(path);
|
|
for (auto i = files.begin(); i != files.end(); ++i)
|
|
{
|
|
if (*i == "VERSION")
|
|
{
|
|
haveVersion = true;
|
|
}
|
|
else if (*i == "LICENSE")
|
|
{
|
|
haveLicense = true;
|
|
}
|
|
}
|
|
|
|
return haveVersion && haveLicense;
|
|
}
|
|
|
|
std::string
|
|
TestRunnerImpl::GetTopLevelSourceDir() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
std::string self = SystemPath::FindSelfDirectory();
|
|
std::list<std::string> elements = SystemPath::Split(self);
|
|
while (!elements.empty())
|
|
{
|
|
std::string path = SystemPath::Join(elements.begin(), elements.end());
|
|
if (IsTopLevelSourceDir(path))
|
|
{
|
|
return path;
|
|
}
|
|
elements.pop_back();
|
|
}
|
|
NS_FATAL_ERROR("Could not find source directory from self=" << self);
|
|
return self;
|
|
}
|
|
|
|
//
|
|
// XML files have restrictions on certain characters that may be present in
|
|
// data. We need to replace these characters with their alternate
|
|
// representation on the way into the XML file.
|
|
//
|
|
std::string
|
|
TestRunnerImpl::ReplaceXmlSpecialCharacters(std::string xml) const
|
|
{
|
|
NS_LOG_FUNCTION(this << xml);
|
|
typedef std::map<char, std::string> specials_map;
|
|
specials_map specials;
|
|
specials['<'] = "<";
|
|
specials['>'] = ">";
|
|
specials['&'] = "&";
|
|
specials['"'] = "'";
|
|
specials['\''] = """;
|
|
|
|
std::string result;
|
|
std::size_t length = xml.length();
|
|
|
|
for (size_t i = 0; i < length; ++i)
|
|
{
|
|
char character = xml[i];
|
|
|
|
auto it = specials.find(character);
|
|
|
|
if (it == specials.end())
|
|
{
|
|
result.push_back(character);
|
|
}
|
|
else
|
|
{
|
|
result += it->second;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** Helper to indent output a specified number of steps. */
|
|
struct Indent
|
|
{
|
|
/**
|
|
* Constructor.
|
|
* \param [in] level The number of steps. A step is " ".
|
|
*/
|
|
Indent(int level);
|
|
/** The number of steps. */
|
|
int level;
|
|
};
|
|
|
|
Indent::Indent(int _level)
|
|
: level(_level)
|
|
{
|
|
NS_LOG_FUNCTION(this << _level);
|
|
}
|
|
|
|
/**
|
|
* Output streamer for Indent.
|
|
* \param [in,out] os The output stream.
|
|
* \param [in] val The Indent object.
|
|
* \returns The stream.
|
|
*/
|
|
std::ostream&
|
|
operator<<(std::ostream& os, const Indent& val)
|
|
{
|
|
for (int i = 0; i < val.level; i++)
|
|
{
|
|
os << " ";
|
|
}
|
|
return os;
|
|
}
|
|
|
|
void
|
|
TestRunnerImpl::PrintReport(TestCase* test, std::ostream* os, bool xml, int level)
|
|
{
|
|
NS_LOG_FUNCTION(this << test << os << xml << level);
|
|
if (test->m_result == nullptr)
|
|
{
|
|
// Do not print reports for tests that were not run.
|
|
return;
|
|
}
|
|
// Report times in seconds, from ms timer
|
|
const double MS_PER_SEC = 1000.;
|
|
double real = test->m_result->clock.GetElapsedReal() / MS_PER_SEC;
|
|
double user = test->m_result->clock.GetElapsedUser() / MS_PER_SEC;
|
|
double system = test->m_result->clock.GetElapsedSystem() / MS_PER_SEC;
|
|
|
|
std::streamsize oldPrecision = (*os).precision(3);
|
|
*os << std::fixed;
|
|
|
|
std::string statusString = test->IsFailed() ? "FAIL" : "PASS";
|
|
if (xml)
|
|
{
|
|
*os << Indent(level) << "<Test>" << std::endl;
|
|
*os << Indent(level + 1) << "<Name>" << ReplaceXmlSpecialCharacters(test->m_name)
|
|
<< "</Name>" << std::endl;
|
|
*os << Indent(level + 1) << "<Result>" << statusString << "</Result>" << std::endl;
|
|
*os << Indent(level + 1) << "<Time real=\"" << real << "\" user=\"" << user
|
|
<< "\" system=\"" << system << "\"/>" << std::endl;
|
|
for (uint32_t i = 0; i < test->m_result->failure.size(); i++)
|
|
{
|
|
TestCaseFailure failure = test->m_result->failure[i];
|
|
*os << Indent(level + 2) << "<FailureDetails>" << std::endl
|
|
<< Indent(level + 3) << "<Condition>" << ReplaceXmlSpecialCharacters(failure.cond)
|
|
<< "</Condition>" << std::endl
|
|
<< Indent(level + 3) << "<Actual>" << ReplaceXmlSpecialCharacters(failure.actual)
|
|
<< "</Actual>" << std::endl
|
|
<< Indent(level + 3) << "<Limit>" << ReplaceXmlSpecialCharacters(failure.limit)
|
|
<< "</Limit>" << std::endl
|
|
<< Indent(level + 3) << "<Message>" << ReplaceXmlSpecialCharacters(failure.message)
|
|
<< "</Message>" << std::endl
|
|
<< Indent(level + 3) << "<File>" << ReplaceXmlSpecialCharacters(failure.file)
|
|
<< "</File>" << std::endl
|
|
<< Indent(level + 3) << "<Line>" << failure.line << "</Line>" << std::endl
|
|
<< Indent(level + 2) << "</FailureDetails>" << std::endl;
|
|
}
|
|
for (uint32_t i = 0; i < test->m_children.size(); i++)
|
|
{
|
|
TestCase* child = test->m_children[i];
|
|
PrintReport(child, os, xml, level + 1);
|
|
}
|
|
*os << Indent(level) << "</Test>" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
*os << Indent(level) << statusString << " " << test->GetName() << " " << real << " s"
|
|
<< std::endl;
|
|
if (m_verbose)
|
|
{
|
|
for (uint32_t i = 0; i < test->m_result->failure.size(); i++)
|
|
{
|
|
*os << Indent(level) << test->m_result->failure[i] << std::endl;
|
|
}
|
|
for (uint32_t i = 0; i < test->m_children.size(); i++)
|
|
{
|
|
TestCase* child = test->m_children[i];
|
|
PrintReport(child, os, xml, level + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
(*os).unsetf(std::ios_base::floatfield);
|
|
(*os).precision(oldPrecision);
|
|
}
|
|
|
|
void
|
|
TestRunnerImpl::PrintHelp(const char* program_name) const
|
|
{
|
|
NS_LOG_FUNCTION(this << program_name);
|
|
std::cout
|
|
<< "Usage: " << program_name << " [OPTIONS]" << std::endl
|
|
<< std::endl
|
|
<< "Options: " << std::endl
|
|
<< " --help : print these options" << std::endl
|
|
<< " --print-test-name-list : print the list of names of tests available" << std::endl
|
|
<< " --list : an alias for --print-test-name-list" << std::endl
|
|
<< " --print-test-types : print the type of tests along with their names" << std::endl
|
|
<< " --print-test-type-list : print the list of types of tests available" << std::endl
|
|
<< " --print-temp-dir : print name of temporary directory before running "
|
|
<< std::endl
|
|
<< " the tests" << std::endl
|
|
<< " --test-type=TYPE : process only tests of type TYPE" << std::endl
|
|
<< " --test-name=NAME : process only test whose name matches NAME" << std::endl
|
|
<< " --suite=NAME : an alias (here for compatibility reasons only) " << std::endl
|
|
<< " for --test-name=NAME" << std::endl
|
|
<< " --assert-on-failure : when a test fails, crash immediately (useful" << std::endl
|
|
<< " when running under a debugger" << std::endl
|
|
<< " --stop-on-failure : when a test fails, stop immediately" << std::endl
|
|
<< " --fullness=FULLNESS : choose the duration of tests to run: QUICK, " << std::endl
|
|
<< " EXTENSIVE, or TAKES_FOREVER, where EXTENSIVE " << std::endl
|
|
<< " includes QUICK and TAKES_FOREVER includes " << std::endl
|
|
<< " QUICK and EXTENSIVE (only QUICK tests are " << std::endl
|
|
<< " run by default)" << std::endl
|
|
<< " --verbose : print details of test execution" << std::endl
|
|
<< " --xml : format test run output as xml" << std::endl
|
|
<< " --tempdir=DIR : set temp dir for tests to store output files" << std::endl
|
|
<< " --datadir=DIR : set data dir for tests to read reference files" << std::endl
|
|
<< " --out=FILE : send test result to FILE instead of standard "
|
|
<< "output" << std::endl
|
|
<< " --append=FILE : append test result to FILE instead of standard "
|
|
<< "output" << std::endl;
|
|
}
|
|
|
|
void
|
|
TestRunnerImpl::PrintTestNameList(std::list<TestCase*>::const_iterator begin,
|
|
std::list<TestCase*>::const_iterator end,
|
|
bool printTestType) const
|
|
{
|
|
NS_LOG_FUNCTION(this << &begin << &end << printTestType);
|
|
std::map<TestSuite::Type, std::string> label;
|
|
|
|
label[TestSuite::Type::ALL] = "all ";
|
|
label[TestSuite::Type::UNIT] = "unit ";
|
|
label[TestSuite::Type::SYSTEM] = "system ";
|
|
label[TestSuite::Type::EXAMPLE] = "example ";
|
|
label[TestSuite::Type::PERFORMANCE] = "performance ";
|
|
|
|
for (auto i = begin; i != end; ++i)
|
|
{
|
|
auto test = dynamic_cast<TestSuite*>(*i);
|
|
NS_ASSERT(test != nullptr);
|
|
if (printTestType)
|
|
{
|
|
std::cout << label[test->GetTestType()];
|
|
}
|
|
std::cout << test->GetName() << std::endl;
|
|
}
|
|
}
|
|
|
|
void
|
|
TestRunnerImpl::PrintTestTypeList() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
std::cout << " core: Run all TestSuite-based tests (exclude examples)" << std::endl;
|
|
std::cout << " example: Examples (to see if example programs run successfully)"
|
|
<< std::endl;
|
|
std::cout
|
|
<< " performance: Performance Tests (check to see if the system is as fast as expected)"
|
|
<< std::endl;
|
|
std::cout << " system: System Tests (spans modules to check integration of modules)"
|
|
<< std::endl;
|
|
std::cout << " unit: Unit Tests (within modules to check basic functionality)"
|
|
<< std::endl;
|
|
}
|
|
|
|
std::list<TestCase*>
|
|
TestRunnerImpl::FilterTests(std::string testName,
|
|
TestSuite::Type testType,
|
|
TestCase::Duration maximumTestDuration)
|
|
{
|
|
NS_LOG_FUNCTION(this << testName << testType);
|
|
std::list<TestCase*> tests;
|
|
for (uint32_t i = 0; i < m_suites.size(); ++i)
|
|
{
|
|
TestSuite* test = m_suites[i];
|
|
if (testType != TestSuite::Type::ALL && test->GetTestType() != testType)
|
|
{
|
|
// skip test
|
|
continue;
|
|
}
|
|
if (!testName.empty() && test->GetName() != testName)
|
|
{
|
|
// skip test
|
|
continue;
|
|
}
|
|
|
|
// Remove any test cases that should be skipped.
|
|
for (auto j = test->m_children.begin(); j != test->m_children.end();)
|
|
{
|
|
TestCase* testCase = *j;
|
|
|
|
// If this test case takes longer than the maximum test
|
|
// duration that should be run, then don't run it.
|
|
if (testCase->m_duration > maximumTestDuration)
|
|
{
|
|
// Free this test case's memory.
|
|
delete *j;
|
|
|
|
// Remove this test case from the test suite.
|
|
j = test->m_children.erase(j);
|
|
}
|
|
else
|
|
{
|
|
// Only advance through the vector elements if this test
|
|
// case wasn't deleted.
|
|
++j;
|
|
}
|
|
}
|
|
|
|
// Add this test suite.
|
|
tests.push_back(test);
|
|
}
|
|
return tests;
|
|
}
|
|
|
|
int
|
|
TestRunnerImpl::Run(int argc, char* argv[])
|
|
{
|
|
NS_LOG_FUNCTION(this << argc << argv);
|
|
std::string testName = "";
|
|
std::string testTypeString = "";
|
|
std::string out = "";
|
|
std::string fullness = "";
|
|
bool xml = false;
|
|
bool append = false;
|
|
bool printTempDir = false;
|
|
bool printTestTypeList = false;
|
|
bool printTestNameList = false;
|
|
bool printTestTypeAndName = false;
|
|
TestCase::Duration maximumTestDuration = TestCase::Duration::QUICK;
|
|
char* progname = argv[0];
|
|
|
|
char** argi = argv;
|
|
++argi;
|
|
|
|
while (*argi != nullptr)
|
|
{
|
|
std::string arg = *argi;
|
|
|
|
if (arg == "--assert-on-failure")
|
|
{
|
|
m_assertOnFailure = true;
|
|
}
|
|
else if (arg == "--stop-on-failure")
|
|
{
|
|
m_continueOnFailure = false;
|
|
}
|
|
else if (arg == "--verbose")
|
|
{
|
|
m_verbose = true;
|
|
}
|
|
else if (arg == "--print-temp-dir")
|
|
{
|
|
printTempDir = true;
|
|
}
|
|
else if (arg == "--update-data")
|
|
{
|
|
m_updateData = true;
|
|
}
|
|
else if (arg == "--print-test-name-list" || arg == "--list")
|
|
{
|
|
printTestNameList = true;
|
|
}
|
|
else if (arg == "--print-test-types")
|
|
{
|
|
printTestTypeAndName = true;
|
|
}
|
|
else if (arg == "--print-test-type-list")
|
|
{
|
|
printTestTypeList = true;
|
|
}
|
|
else if (arg == "--append")
|
|
{
|
|
append = true;
|
|
}
|
|
else if (arg == "--xml")
|
|
{
|
|
xml = true;
|
|
}
|
|
else if (arg.find("--test-type=") != std::string::npos)
|
|
{
|
|
testTypeString = arg.substr(arg.find_first_of('=') + 1);
|
|
}
|
|
else if (arg.find("--test-name=") != std::string::npos ||
|
|
arg.find("--suite=") != std::string::npos)
|
|
{
|
|
testName = arg.substr(arg.find_first_of('=') + 1);
|
|
}
|
|
else if (arg.find("--tempdir=") != std::string::npos)
|
|
{
|
|
m_tempDir = arg.substr(arg.find_first_of('=') + 1);
|
|
}
|
|
else if (arg.find("--out=") != std::string::npos)
|
|
{
|
|
out = arg.substr(arg.find_first_of('=') + 1);
|
|
}
|
|
else if (arg.find("--fullness=") != std::string::npos)
|
|
{
|
|
fullness = arg.substr(arg.find_first_of('=') + 1);
|
|
|
|
// Set the maximum test length allowed.
|
|
if (fullness == "QUICK")
|
|
{
|
|
maximumTestDuration = TestCase::Duration::QUICK;
|
|
}
|
|
else if (fullness == "EXTENSIVE")
|
|
{
|
|
maximumTestDuration = TestCase::Duration::EXTENSIVE;
|
|
}
|
|
else if (fullness == "TAKES_FOREVER")
|
|
{
|
|
maximumTestDuration = TestCase::Duration::TAKES_FOREVER;
|
|
}
|
|
else
|
|
{
|
|
// Wrong fullness option
|
|
PrintHelp(progname);
|
|
return 3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Print the help if arg == "--help" or arg is an un-recognized command-line argument
|
|
PrintHelp(progname);
|
|
return 0;
|
|
}
|
|
argi++;
|
|
}
|
|
TestSuite::Type testType;
|
|
if (testTypeString.empty() || testTypeString == "core")
|
|
{
|
|
testType = TestSuite::Type::ALL;
|
|
}
|
|
else if (testTypeString == "example")
|
|
{
|
|
testType = TestSuite::Type::EXAMPLE;
|
|
}
|
|
else if (testTypeString == "unit")
|
|
{
|
|
testType = TestSuite::Type::UNIT;
|
|
}
|
|
else if (testTypeString == "system")
|
|
{
|
|
testType = TestSuite::Type::SYSTEM;
|
|
}
|
|
else if (testTypeString == "performance")
|
|
{
|
|
testType = TestSuite::Type::PERFORMANCE;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Invalid test type specified: " << testTypeString << std::endl;
|
|
PrintTestTypeList();
|
|
return 1;
|
|
}
|
|
|
|
std::list<TestCase*> tests = FilterTests(testName, testType, maximumTestDuration);
|
|
|
|
if (m_tempDir.empty())
|
|
{
|
|
m_tempDir = SystemPath::MakeTemporaryDirectoryName();
|
|
}
|
|
if (printTempDir)
|
|
{
|
|
std::cout << m_tempDir << std::endl;
|
|
}
|
|
if (printTestNameList)
|
|
{
|
|
PrintTestNameList(tests.begin(), tests.end(), printTestTypeAndName);
|
|
return 0;
|
|
}
|
|
if (printTestTypeList)
|
|
{
|
|
PrintTestTypeList();
|
|
return 0;
|
|
}
|
|
|
|
std::ostream* os;
|
|
if (!out.empty())
|
|
{
|
|
std::ofstream* ofs;
|
|
ofs = new std::ofstream();
|
|
std::ios_base::openmode mode = std::ios_base::out;
|
|
if (append)
|
|
{
|
|
mode |= std::ios_base::app;
|
|
}
|
|
else
|
|
{
|
|
mode |= std::ios_base::trunc;
|
|
}
|
|
ofs->open(out, mode);
|
|
os = ofs;
|
|
}
|
|
else
|
|
{
|
|
os = &std::cout;
|
|
}
|
|
|
|
// let's run our tests now.
|
|
bool failed = false;
|
|
if (tests.empty())
|
|
{
|
|
std::cerr << "Error: no tests match the requested string" << std::endl;
|
|
return 1;
|
|
}
|
|
else if (tests.size() > 1)
|
|
{
|
|
std::cerr << "Error: tests should be launched separately (one at a time)" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
for (auto i = tests.begin(); i != tests.end(); ++i)
|
|
{
|
|
TestCase* test = *i;
|
|
|
|
#ifdef ENABLE_DES_METRICS
|
|
{
|
|
/*
|
|
Reorganize argv
|
|
Since DES Metrics uses argv[0] for the trace file name,
|
|
grab the test name and put it in argv[0],
|
|
with test-runner as argv[1]
|
|
then the rest of the original arguments.
|
|
*/
|
|
std::string testname = test->GetName();
|
|
std::string runner = "[" + SystemPath::Split(argv[0]).back() + "]";
|
|
|
|
std::vector<std::string> desargs;
|
|
desargs.push_back(testname);
|
|
desargs.push_back(runner);
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
desargs.push_back(argv[i]);
|
|
}
|
|
|
|
DesMetrics::Get()->Initialize(desargs, m_tempDir);
|
|
}
|
|
#endif
|
|
|
|
test->Run(this);
|
|
PrintReport(test, os, xml, 0);
|
|
if (test->IsFailed())
|
|
{
|
|
failed = true;
|
|
if (!m_continueOnFailure)
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!out.empty())
|
|
{
|
|
delete os;
|
|
}
|
|
|
|
return failed ? 1 : 0;
|
|
}
|
|
|
|
int
|
|
TestRunner::Run(int argc, char* argv[])
|
|
{
|
|
NS_LOG_FUNCTION(argc << argv);
|
|
return TestRunnerImpl::Get()->Run(argc, argv);
|
|
}
|
|
|
|
std::ostream&
|
|
operator<<(std::ostream& os, TestSuite::Type type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case TestSuite::Type::ALL:
|
|
return os << "ALL";
|
|
case TestSuite::Type::UNIT:
|
|
return os << "UNIT";
|
|
case TestSuite::Type::SYSTEM:
|
|
return os << "SYSTEM";
|
|
case TestSuite::Type::EXAMPLE:
|
|
return os << "EXAMPLE";
|
|
case TestSuite::Type::PERFORMANCE:
|
|
return os << "PERFORMANCE";
|
|
};
|
|
return os << "UNKNOWN(" << static_cast<uint32_t>(type) << ")";
|
|
}
|
|
|
|
std::ostream&
|
|
operator<<(std::ostream& os, TestCase::Duration duration)
|
|
{
|
|
switch (duration)
|
|
{
|
|
case TestCase::Duration::QUICK:
|
|
return os << "QUICK";
|
|
case TestCase::Duration::EXTENSIVE:
|
|
return os << "EXTENSIVE";
|
|
case TestCase::Duration::TAKES_FOREVER:
|
|
return os << "TAKES_FOREVER";
|
|
};
|
|
return os << "UNKNOWN(" << static_cast<uint32_t>(duration) << ")";
|
|
}
|
|
|
|
} // namespace ns3
|