775 lines
14 KiB
C++
775 lines
14 KiB
C++
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
|
|
/*
|
|
* 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 "assert.h"
|
|
#include "abort.h"
|
|
#include <math.h>
|
|
|
|
namespace ns3 {
|
|
|
|
|
|
//
|
|
// 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
|
|
ReplaceXmlSpecialCharacters (std::string xml)
|
|
{
|
|
std::string specials = "<>&\"'";
|
|
std::string replacements[] = {"<", ">", "&", "'", """};
|
|
std::string result;
|
|
std::size_t index, length = xml.length ();
|
|
|
|
for (size_t i = 0; i < length; ++i)
|
|
{
|
|
char character = xml[i];
|
|
|
|
if ((index = specials.find (character)) == std::string::npos)
|
|
{
|
|
result.push_back (character);
|
|
}
|
|
else
|
|
{
|
|
result += replacements[index];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
TestDoubleIsEqual (const double x1, const double x2, const double epsilon)
|
|
{
|
|
int exponent;
|
|
double delta, difference;
|
|
|
|
//
|
|
// Find exponent of largest absolute value
|
|
//
|
|
{
|
|
double max = (fabs (x1) > fabs (x2)) ? x1 : x2;
|
|
frexp (max, &exponent);
|
|
}
|
|
|
|
//
|
|
// Form a neighborhood of size 2 * delta
|
|
//
|
|
delta = ldexp (epsilon, exponent);
|
|
difference = x1 - x2;
|
|
|
|
if (difference > delta || difference < -delta)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
TestCase::TestCase (std::string name)
|
|
: m_name (name),
|
|
m_verbose (false),
|
|
m_continueOnFailure (false),
|
|
m_detailsReported (false),
|
|
m_basedir ("invalid"),
|
|
m_tempdir ("invalid"),
|
|
m_ofs (0),
|
|
m_error (false)
|
|
{
|
|
}
|
|
|
|
TestCase::~TestCase ()
|
|
{
|
|
}
|
|
|
|
void
|
|
TestCase::ReportStart (void)
|
|
{
|
|
DoReportStart ();
|
|
}
|
|
|
|
void
|
|
TestCase::ReportCaseSuccess (void)
|
|
{
|
|
UpdateErrorStatus (false);
|
|
DoReportCaseSuccess ();
|
|
}
|
|
|
|
void
|
|
TestCase::ReportCaseFailure (void)
|
|
{
|
|
UpdateErrorStatus (true);
|
|
DoReportCaseFailure ();
|
|
}
|
|
|
|
void
|
|
TestCase::ReportTestFailure (
|
|
std::string cond,
|
|
std::string actual,
|
|
std::string limit,
|
|
std::string message,
|
|
std::string file,
|
|
int32_t line)
|
|
{
|
|
UpdateErrorStatus (true);
|
|
DoReportTestFailure (cond, actual, limit, message, file, line);
|
|
m_detailsReported = true;
|
|
}
|
|
|
|
void
|
|
TestCase::ReportEnd (void)
|
|
{
|
|
DoReportStart ();
|
|
}
|
|
|
|
bool
|
|
TestCase::Run (void)
|
|
{
|
|
//
|
|
// We set up a flag to make sure the user plays by the rules and actually
|
|
// does something to report the details of an error.
|
|
//
|
|
m_detailsReported = false;
|
|
|
|
DoReportStart ();
|
|
DoSetup ();
|
|
|
|
bool result = DoRun ();
|
|
UpdateErrorStatus (result);
|
|
|
|
DoTeardown ();
|
|
|
|
if (GetErrorStatus () == false)
|
|
{
|
|
DoReportCaseSuccess ();
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// It is a programming error to return an error from a test case without
|
|
// calling ReportTestFailure. Typically this is done automagically when
|
|
// using the ASSERT or EXPECT macros. If you don't use these, you must
|
|
// ReportTestFailure on any errors yourself, which will set
|
|
// m_detailsReported and make us happy.
|
|
//
|
|
NS_ASSERT_MSG (m_detailsReported, "The details of a failing test was not reported");
|
|
|
|
DoReportCaseFailure ();
|
|
}
|
|
|
|
DoReportEnd ();
|
|
|
|
return GetErrorStatus ();
|
|
}
|
|
|
|
void
|
|
TestCase::SetVerbose (bool verbose)
|
|
{
|
|
m_verbose = verbose;
|
|
}
|
|
|
|
void
|
|
TestCase::SetContinueOnFailure (bool continueOnFailure)
|
|
{
|
|
m_continueOnFailure = continueOnFailure;
|
|
}
|
|
|
|
void
|
|
TestCase::SetName (std::string name)
|
|
{
|
|
m_name = name;
|
|
}
|
|
|
|
std::string
|
|
TestCase::GetName (void)
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
void
|
|
TestCase::SetBaseDir (std::string basedir)
|
|
{
|
|
//
|
|
// C and C++ allow one to use forward slashes even on systems where the
|
|
// separator is actually a backslash.
|
|
//
|
|
if (basedir[basedir.length () - 1] != '/')
|
|
{
|
|
m_basedir = basedir + "/";
|
|
}
|
|
else
|
|
{
|
|
m_basedir = basedir;
|
|
}
|
|
}
|
|
|
|
std::string
|
|
TestCase::GetBaseDir (void)
|
|
{
|
|
return m_basedir;
|
|
}
|
|
|
|
void
|
|
TestCase::SetTempDir (std::string tempdir)
|
|
{
|
|
//
|
|
// C and C++ allow one to use forward slashes even on systems where the
|
|
// separator is actually a backslash.
|
|
//
|
|
if (tempdir[tempdir.length () - 1] != '/')
|
|
{
|
|
m_tempdir = tempdir + "/";
|
|
}
|
|
else
|
|
{
|
|
m_tempdir = tempdir;
|
|
}
|
|
}
|
|
|
|
std::string
|
|
TestCase::GetTempDir (void)
|
|
{
|
|
return m_tempdir;
|
|
}
|
|
|
|
std::string
|
|
TestCase::GetSourceDir (std::string file)
|
|
{
|
|
//
|
|
// The <file> parameter is actually going to be __FILE__ which may have
|
|
// backslashes in it on win32 systems. For example,
|
|
//
|
|
// ..\src\common\pcap-file-test-suite.cc (win32)
|
|
//
|
|
// or
|
|
//
|
|
// ../src/common/pcap-file-test-suite.cc (grown-up systems)
|
|
//
|
|
#ifdef WIN32
|
|
std::string::size_type relPathBegin = file.find_first_of ("\\");
|
|
std::string::size_type relPathEnd = file.find_last_of ("\\");
|
|
#else
|
|
std::string::size_type relPathBegin = file.find_first_of ("/");
|
|
std::string::size_type relPathEnd = file.find_last_of ("/");
|
|
#endif
|
|
|
|
NS_ABORT_MSG_IF (relPathBegin == std::string::npos, "TestCase::GetSourceDir(): Internal Error");
|
|
NS_ABORT_MSG_IF (relPathEnd == std::string::npos, "TestCase::GetSourceDir(): Internal Error");
|
|
|
|
return GetBaseDir () + file.substr (relPathBegin, relPathEnd + 1 - relPathBegin);
|
|
}
|
|
|
|
void
|
|
TestCase::SetStream (std::ofstream *ofs)
|
|
{
|
|
m_ofs = ofs;
|
|
}
|
|
|
|
std::ofstream *
|
|
TestCase::GetStream (void)
|
|
{
|
|
return m_ofs;
|
|
}
|
|
|
|
void
|
|
TestCase::UpdateErrorStatus (bool error)
|
|
{
|
|
m_error |= error;
|
|
}
|
|
|
|
void
|
|
TestCase::SetErrorStatus (bool error)
|
|
{
|
|
m_error = error;
|
|
}
|
|
|
|
bool
|
|
TestCase::GetErrorStatus (void)
|
|
{
|
|
return m_error;
|
|
}
|
|
|
|
bool
|
|
TestCase::ContinueOnFailure (void)
|
|
{
|
|
return m_continueOnFailure;
|
|
}
|
|
|
|
void
|
|
TestCase::DoReportStart (void)
|
|
{
|
|
m_msClock.Start ();
|
|
|
|
if (m_ofs == 0)
|
|
{
|
|
return;
|
|
}
|
|
*m_ofs << " <TestCase>" << std::endl;
|
|
*m_ofs << " <CaseName>" << ReplaceXmlSpecialCharacters (GetName ()) << "</CaseName>" << std::endl;
|
|
}
|
|
|
|
void
|
|
TestCase::DoReportCaseSuccess (void)
|
|
{
|
|
if (m_ofs == 0)
|
|
{
|
|
return;
|
|
}
|
|
*m_ofs << " <CaseResult>PASS</CaseResult>" << std::endl;
|
|
}
|
|
|
|
void
|
|
TestCase::DoReportCaseFailure (void)
|
|
{
|
|
if (m_ofs == 0)
|
|
{
|
|
return;
|
|
}
|
|
*m_ofs << " <CaseResult>FAIL</CaseResult>" << std::endl;
|
|
}
|
|
|
|
void
|
|
TestCase::DoReportTestFailure (
|
|
std::string cond,
|
|
std::string actual,
|
|
std::string limit,
|
|
std::string message,
|
|
std::string file,
|
|
int32_t line)
|
|
{
|
|
if (m_ofs == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
*m_ofs << " <FailureDetails>" << std::endl;
|
|
*m_ofs << " <Condition>" << ReplaceXmlSpecialCharacters (cond) << "</Condition>" << std::endl;
|
|
*m_ofs << " <Actual>" << ReplaceXmlSpecialCharacters (actual) << "</Actual>" << std::endl;
|
|
*m_ofs << " <Limit>" << ReplaceXmlSpecialCharacters (limit) << "</Limit>" << std::endl;
|
|
*m_ofs << " <Message>" << ReplaceXmlSpecialCharacters (message) << "</Message>" << std::endl;
|
|
*m_ofs << " <File>" << ReplaceXmlSpecialCharacters (file) << "</File>" << std::endl;
|
|
*m_ofs << " <Line>" << line << "</Line>" << std::endl;
|
|
*m_ofs << " </FailureDetails>" << std::endl;
|
|
}
|
|
|
|
void
|
|
TestCase::DoReportEnd (void)
|
|
{
|
|
m_msClock.End ();
|
|
|
|
if (m_ofs == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
(*m_ofs).precision (3);
|
|
*m_ofs << std::fixed;
|
|
|
|
const double MS_PER_SEC = 1000.;
|
|
|
|
*m_ofs << " <CaseTime>" << "real " << m_msClock.GetElapsedReal () / MS_PER_SEC
|
|
<< " user " << m_msClock.GetElapsedUser () / MS_PER_SEC
|
|
<< " system " << m_msClock.GetElapsedSystem () / MS_PER_SEC
|
|
<< "</CaseTime>" << std::endl;
|
|
|
|
*m_ofs << " </TestCase>" << std::endl;
|
|
}
|
|
|
|
void
|
|
TestCase::DoSetup (void)
|
|
{
|
|
}
|
|
|
|
void
|
|
TestCase::DoTeardown (void)
|
|
{
|
|
}
|
|
|
|
TestSuite::TestSuite (std::string name, TestType type)
|
|
: m_name (name),
|
|
m_verbose (false),
|
|
m_basedir ("invalid"),
|
|
m_tempdir ("invalid"),
|
|
m_ofs (0),
|
|
m_error (false),
|
|
m_type (type)
|
|
{
|
|
TestRunner::AddTestSuite (this);
|
|
}
|
|
|
|
TestSuite::~TestSuite ()
|
|
{
|
|
for (TestCaseVector_t::iterator i = m_tests.begin (); i != m_tests.end (); ++i)
|
|
{
|
|
delete *i;
|
|
*i = 0;
|
|
}
|
|
|
|
m_tests.erase (m_tests.begin (), m_tests.end ());
|
|
}
|
|
|
|
void
|
|
TestSuite::ReportStart (void)
|
|
{
|
|
DoReportStart ();
|
|
}
|
|
|
|
void
|
|
TestSuite::ReportSuccess (void)
|
|
{
|
|
UpdateErrorStatus (false);
|
|
DoReportSuccess ();
|
|
}
|
|
|
|
void
|
|
TestSuite::ReportFailure (void)
|
|
{
|
|
UpdateErrorStatus (true);
|
|
DoReportFailure ();
|
|
}
|
|
|
|
void
|
|
TestSuite::ReportEnd (void)
|
|
{
|
|
DoReportEnd ();
|
|
}
|
|
|
|
bool
|
|
TestSuite::Run (void)
|
|
{
|
|
DoReportStart ();
|
|
|
|
DoSetup ();
|
|
|
|
bool result = DoRun ();
|
|
UpdateErrorStatus (result);
|
|
|
|
DoTeardown ();
|
|
|
|
if (GetErrorStatus () == false)
|
|
{
|
|
DoReportSuccess ();
|
|
}
|
|
else
|
|
{
|
|
DoReportFailure ();
|
|
}
|
|
|
|
DoReportEnd ();
|
|
|
|
return GetErrorStatus ();
|
|
}
|
|
|
|
uint32_t
|
|
TestSuite::AddTestCase (TestCase *testCase)
|
|
{
|
|
uint32_t index = m_tests.size ();
|
|
m_tests.push_back (testCase);
|
|
return index;
|
|
}
|
|
|
|
uint32_t
|
|
TestSuite::GetNTestCases (void)
|
|
{
|
|
return m_tests.size ();
|
|
}
|
|
|
|
TestCase *
|
|
TestSuite::GetTestCase (uint32_t n)
|
|
{
|
|
return m_tests[n];
|
|
}
|
|
|
|
TestSuite::TestType
|
|
TestSuite::GetTestType (void)
|
|
{
|
|
return m_type;
|
|
}
|
|
|
|
void
|
|
TestSuite::SetVerbose (bool verbose)
|
|
{
|
|
m_verbose = verbose;
|
|
}
|
|
|
|
void
|
|
TestSuite::SetContinueOnFailure (bool continueOnFailure)
|
|
{
|
|
m_continueOnFailure = continueOnFailure;
|
|
}
|
|
|
|
void
|
|
TestSuite::SetName (std::string name)
|
|
{
|
|
m_name = name;
|
|
}
|
|
|
|
std::string
|
|
TestSuite::GetName (void)
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
void
|
|
TestSuite::SetBaseDir (std::string basedir)
|
|
{
|
|
//
|
|
// C and C++ allow one to use forward slashes even on systems where the
|
|
// separator is actually a backslash.
|
|
//
|
|
if (basedir[basedir.length () - 1] != '/')
|
|
{
|
|
m_basedir = basedir + "/";
|
|
}
|
|
else
|
|
{
|
|
m_basedir = basedir;
|
|
}
|
|
}
|
|
|
|
std::string
|
|
TestSuite::GetBaseDir (void)
|
|
{
|
|
return m_basedir;
|
|
}
|
|
|
|
void
|
|
TestSuite::SetTempDir (std::string tempdir)
|
|
{
|
|
//
|
|
// C and C++ allow one to use forward slashes even on systems where the
|
|
// separator is actually a backslash.
|
|
//
|
|
if (tempdir[tempdir.length () - 1] != '/')
|
|
{
|
|
m_tempdir = tempdir + "/";
|
|
}
|
|
else
|
|
{
|
|
m_tempdir = tempdir;
|
|
}
|
|
}
|
|
|
|
std::string
|
|
TestSuite::GetTempDir (void)
|
|
{
|
|
return m_tempdir;
|
|
}
|
|
|
|
void
|
|
TestSuite::SetStream (std::ofstream *ofs)
|
|
{
|
|
m_ofs = ofs;
|
|
}
|
|
|
|
void
|
|
TestSuite::UpdateErrorStatus (bool error)
|
|
{
|
|
m_error |= error;
|
|
}
|
|
|
|
void
|
|
TestSuite::SetErrorStatus (bool error)
|
|
{
|
|
m_error = error;
|
|
}
|
|
|
|
bool
|
|
TestSuite::GetErrorStatus (void)
|
|
{
|
|
return m_error;
|
|
}
|
|
|
|
bool
|
|
TestSuite::ContinueOnFailure (void)
|
|
{
|
|
return m_continueOnFailure;
|
|
}
|
|
|
|
void
|
|
TestSuite::DoReportStart (void)
|
|
{
|
|
m_msClock.Start ();
|
|
|
|
if (m_ofs == 0)
|
|
{
|
|
return;
|
|
}
|
|
*m_ofs << "<TestSuite>" << std::endl;
|
|
*m_ofs << " <SuiteName>" << ReplaceXmlSpecialCharacters (GetName ()) << "</SuiteName>" << std::endl;
|
|
}
|
|
|
|
void
|
|
TestSuite::DoReportFailure (void)
|
|
{
|
|
if (m_ofs == 0)
|
|
{
|
|
return;
|
|
}
|
|
*m_ofs << " <SuiteResult>FAIL</SuiteResult>" << std::endl;
|
|
}
|
|
|
|
void
|
|
TestSuite::DoReportSuccess (void)
|
|
{
|
|
if (m_ofs == 0)
|
|
{
|
|
return;
|
|
}
|
|
*m_ofs << " <SuiteResult>PASS</SuiteResult>" << std::endl;
|
|
}
|
|
|
|
void
|
|
TestSuite::DoReportEnd (void)
|
|
{
|
|
m_msClock.End ();
|
|
|
|
if (m_ofs == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
(*m_ofs).precision (3);
|
|
*m_ofs << std::fixed;
|
|
|
|
const double MS_PER_SEC = 1000.;
|
|
|
|
*m_ofs << " <SuiteTime>" << "real " << m_msClock.GetElapsedReal () / MS_PER_SEC
|
|
<< " user " << m_msClock.GetElapsedUser () / MS_PER_SEC
|
|
<< " system " << m_msClock.GetElapsedSystem () / MS_PER_SEC
|
|
<< "</SuiteTime>" << std::endl;
|
|
|
|
*m_ofs << "</TestSuite>" << std::endl;
|
|
}
|
|
|
|
void
|
|
TestSuite::DoSetup (void)
|
|
{
|
|
}
|
|
|
|
bool
|
|
TestSuite::DoRun (void)
|
|
{
|
|
SetErrorStatus (false);
|
|
|
|
for (TestCaseVector_t::iterator i = m_tests.begin (); i != m_tests.end (); ++i)
|
|
{
|
|
//
|
|
// Set the current options for the test case
|
|
//
|
|
(*i)->SetVerbose (m_verbose);
|
|
(*i)->SetContinueOnFailure (m_continueOnFailure);
|
|
(*i)->SetBaseDir (m_basedir);
|
|
(*i)->SetTempDir (m_tempdir);
|
|
(*i)->SetStream (m_ofs);
|
|
|
|
//
|
|
// Run the test case
|
|
//
|
|
bool result = (*i)->Run ();
|
|
UpdateErrorStatus (result);
|
|
|
|
//
|
|
// Exit if we have detected an error and we are not allowing multiple filures
|
|
//
|
|
if (GetErrorStatus () && m_continueOnFailure == false)
|
|
{
|
|
return GetErrorStatus ();
|
|
}
|
|
}
|
|
|
|
return GetErrorStatus ();
|
|
}
|
|
|
|
void
|
|
TestSuite::DoTeardown (void)
|
|
{
|
|
}
|
|
|
|
class TestRunnerImpl
|
|
{
|
|
public:
|
|
uint32_t AddTestSuite (TestSuite *testSuite);
|
|
uint32_t GetNTestSuites (void);
|
|
TestSuite *GetTestSuite (uint32_t n);
|
|
bool RunTestSuite (uint32_t n);
|
|
|
|
static TestRunnerImpl *Instance (void);
|
|
private:
|
|
TestRunnerImpl ();
|
|
~TestRunnerImpl ();
|
|
|
|
typedef std::vector<TestSuite *> TestSuiteVector_t;
|
|
TestSuiteVector_t m_suites;
|
|
};
|
|
|
|
TestRunnerImpl::TestRunnerImpl ()
|
|
{
|
|
}
|
|
|
|
TestRunnerImpl::~TestRunnerImpl ()
|
|
{
|
|
}
|
|
|
|
TestRunnerImpl *
|
|
TestRunnerImpl::Instance (void)
|
|
{
|
|
static TestRunnerImpl runner;
|
|
return &runner;
|
|
}
|
|
|
|
uint32_t
|
|
TestRunnerImpl::AddTestSuite (TestSuite *testSuite)
|
|
{
|
|
uint32_t index = m_suites.size ();
|
|
m_suites.push_back (testSuite);
|
|
return index;
|
|
}
|
|
|
|
uint32_t
|
|
TestRunnerImpl::GetNTestSuites (void)
|
|
{
|
|
return m_suites.size ();
|
|
}
|
|
|
|
TestSuite *
|
|
TestRunnerImpl::GetTestSuite (uint32_t n)
|
|
{
|
|
return m_suites[n];
|
|
}
|
|
|
|
uint32_t
|
|
TestRunner::AddTestSuite (TestSuite *testSuite)
|
|
{
|
|
return TestRunnerImpl::Instance ()->AddTestSuite (testSuite);
|
|
}
|
|
|
|
uint32_t
|
|
TestRunner::GetNTestSuites (void)
|
|
{
|
|
return TestRunnerImpl::Instance ()->GetNTestSuites ();
|
|
}
|
|
|
|
TestSuite *
|
|
TestRunner::GetTestSuite (uint32_t n)
|
|
{
|
|
return TestRunnerImpl::Instance ()->GetTestSuite (n);
|
|
}
|
|
|
|
} // namespace ns3
|