/* -*- 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 "ns3/test.h"
#include "ns3/assert.h"
#include "ns3/abort.h"
#include <iostream>
#include <fstream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
extern bool gBreakOnFailure;
using namespace ns3;
//
// Create a temporary directory for use by test programs. This is not a
// foolproof thing, but a reasonably good way to get a throwaway directory
// while running tests in a debugger.
//
std::string
TempDir (void)
{
char *path = NULL;
path = getenv ("TMP");
if (path == NULL)
{
path = getenv ("TEMP");
if (path == NULL)
{
path = const_cast<char *> ("/tmp");
}
}
//
// Just in case the user wants to go back and find the output, we give
// a hint as to which dir we created by including a time hint.
//
time_t now = time (NULL);
struct tm *tm_now = localtime (&now);
//
// But we also randomize the name in case there are multiple users doing
// this at the same time
//
srand (time (0));
long int n = rand ();
//
// The final path to the directory is going to look something like
//
// /tmp/ns3-14.30.29.32767
//
// The first segment comes from one of the temporary directory env
// variables or /tmp if not found. The directory name starts with an
// identifier telling folks who is making all of the temp directories
// and then the local time (in this case 14.30.29 -- which is 2:30 and
// 29 seconds PM).
//
char dirname[1024];
snprintf (dirname, sizeof(dirname), "%s/ns-3.%d.%d.%d.%ld", path, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec, n);
#if (defined(_WIN32) || defined(_WIN64)) && !defined(__CYGWIN__)
if(mkdir(dirname) == 0)
#else
if (mkdir (dirname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0)
#endif
{
return dirname;
}
else
{
return "";
}
}
//
// Test suites may need to figure out where their source directory is in order
// to find test vectors. To do that they will need to know where the base
// directory of the distribution is (the directory in which "src" is found).
// It is painful for a user debugging test suites to always provide that dir
// so we try and find it in the current directory tree.
//
std::string
BaseDir (void)
{
//
// Get an absolute path to the current working directory. Following code
// depends on the leading '/'
//
char pathbuf[PATH_MAX];
if (getcwd (pathbuf, sizeof(pathbuf)) == NULL)
{
NS_ABORT_MSG ("Basedir(): unable to getcwd()");
}
//
// Walk up the directory tree looking for a directory that has files that
// indicate it is the base of an ns-3 distribution. We use VERSION and
// LICENSE which have been there from the beginning of time.
//
for (;;)
{
bool haveVersion = false;
bool haveLicense = false;
//
// Open the directory file for the current directory and loop through
// the directory entries.
//
DIR *dp = opendir (pathbuf);
if (dp != NULL)
{
while (struct dirent *de = readdir (dp))
{
if (strcmp (de->d_name, "VERSION") == 0)
{
haveVersion = true;
}
if (strcmp (de->d_name, "LICENSE") == 0)
{
haveLicense = true;
}
}
}
closedir (dp);
//
// If there's a file named VERSION and a file named LICENSE in this
// directory, we assume it's our base directory.
//
if (haveVersion && haveLicense)
{
return pathbuf;
}
//
// Strip off the last segment of the current directory.
//
char *last = strrchr (pathbuf, '/');
NS_ASSERT_MSG (last, "No \"/\" found in absolute path ???");
*last = '\0';
if (strlen(pathbuf) == 0)
{
return "";
}
}
//
// Quiet the compiler.
//
return "";
}
//
// Run one of the test suites. Returns an integer with the boolean sense of
// "an error has occurred." That is, 0 == false -> no error; 1 == true -> an
// error occurred.
//
int
main (int argc, char *argv[])
{
bool doVerbose = false;
bool doList = false;
bool doMultiple = false;
bool doHelp = false;
bool doSuite = false;
bool doKinds = false;
gBreakOnFailure = false;
bool haveBasedir = false;
bool haveTempdir = false;
bool haveOutfile = false;
bool haveType = false;
std::string suiteName;
std::string basedir;
std::string tempdir;
std::string outfileName;
std::string typeName;
for (int i = 1; i < argc; ++i)
{
std::string arg(argv[i]);
if (arg.find ("--assert") != std::string::npos)
{
gBreakOnFailure = true;
}
if (arg.find ("--basedir=") != std::string::npos)
{
basedir = arg.substr (arg.find_first_of ("=") + 1, 9999);
haveBasedir = true;
}
if (arg.find ("--constrain=") != std::string::npos)
{
typeName = arg.substr (arg.find_first_of ("=") + 1, 9999);
haveType = true;
}
if (arg.compare ("--help") == 0)
{
doHelp = true;
}
if (arg.compare ("--kinds") == 0)
{
doKinds = true;
}
if (arg.compare ("--list") == 0)
{
doList = true;
}
if (arg.compare ("--multiple") == 0)
{
doMultiple = true;
}
if (arg.find ("--out=") != std::string::npos)
{
outfileName = arg.substr (arg.find_first_of ("=") + 1, 9999);
haveOutfile = true;
}
if (arg.find ("--suite=") != std::string::npos)
{
suiteName = arg.substr (arg.find_first_of ("=") + 1, 9999);
doSuite = true;
}
if (arg.find ("--tempdir=") != std::string::npos)
{
tempdir = arg.substr (arg.find_first_of ("=") + 1, 9999);
haveTempdir = true;
}
if (arg.compare ("--verbose") == 0)
{
doVerbose = true;
}
}
//
// A help request trumps everything else. If we have one, just print the help
// and leave.
//
if (doHelp)
{
std::cout << " --assert: Tell tests to segfault (like assert) if an error is detected" << std::endl;
std::cout << " --basedir=dir: Set the base directory (where to find src) to \"dir\"" << std::endl;
std::cout << " --tempdir=dir: Set the temporary directory (where to find data files) to \"dir\"" << std::endl;
std::cout << " --constrain=test-type: Constrain checks to test suites of type \"test-type\"" << std::endl;
std::cout << " --help: Print this message" << std::endl;
std::cout << " --kinds: List all of the available kinds of tests" << std::endl;
std::cout << " --list: List all of the test suites (optionally constrained by test-type)" << std::endl;
std::cout << " --multiple: Allow test suites and cases to produce multiple failures" << std::endl;
std::cout << " --out=file-name: Set the test status output file to \"file-name\"" << std::endl;
std::cout << " --suite=suite-name: Run the test suite named \"suite-name\"" << std::endl;
std::cout << " --verbose: Turn on messages in the run test suites" << std::endl;
return false;
}
//
// A kinds request trumps everything remaining. If we are asked, just
// print the list of types and leave.
//
if (doKinds)
{
//
// Coming up with a string to represent a test type is completely up to
// us here. We just define the types as being a string composed of the
// enum defined in test.h converted to lower case.
//
std::cout << " bvt: Build Verification Tests (to see if build completed successfully)" << std::endl;
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;
return false;
}
//
// A list request is the first functional request. It trumps running the
// actual tests. If we get a list request, we don't run anything, we just
// do the requested list which may or may not be qualified by a typename.
//
if (doList)
{
for (uint32_t i = 0; i < TestRunner::GetNTestSuites (); ++i)
{
TestSuite *suite = TestRunner::GetTestSuite (i);
//
// Filter the tests listed by type if requested. The special typeName
// "core" means any TestSuite.
//
if (haveType && typeName != "core")
{
TestSuite::TestType type = suite->GetTestType ();
if (typeName == "bvt" && type != TestSuite::BVT)
{
continue;
}
if (typeName == "unit" && type != TestSuite::UNIT)
{
continue;
}
if (typeName == "system" && type != TestSuite::SYSTEM)
{
continue;
}
if (typeName == "example" && type != TestSuite::EXAMPLE)
{
continue;
}
if (typeName == "performance" && type != TestSuite::PERFORMANCE)
{
continue;
}
}
//
// This creates a list of test suite names that can be used by the
// high level test manager to get a list of all tests. It will then
// typically launch individual tests in parallel, calling back here
// with a specific "suite=" to run.
//
std::cout << suite->GetName () << std::endl;
}
return false;
}
//
// We have a lot of options possible to provide flexibility. It can become
// painful, however, to provide all of the options when debugging, and it
// turns out that not all tests require all options. It is really helpful
// to try and put together some reasonable defaults if we're not provided
// them.
//
if (!haveTempdir)
{
//
// No temporary directory was provided. We don't know if the selected
// test or tests will need one, but we can cook something up. The
// tmpnam function has its own set of problems, so we'll just do our
// own thing.
//
tempdir = TempDir ();
if (tempdir.size ())
{
std::cout << "Temporary directory not provided. Using \"" << tempdir << "\"" << std::endl;
haveTempdir = true;
}
else
{
std::cout << "Temporary directory not provided and unable to create one." << std::endl;
return true;
}
}
if (haveBasedir == false)
{
//
// No basedir was provided. If we don't have it, we can try and find it
// in the current directory tree.
//
basedir = BaseDir ();
if (basedir.size ())
{
std::cout << "Base directory not provided. Using \"" << basedir << "\"" << std::endl;
haveBasedir = true;
}
else
{
std::cout << "Base directory not provided and unable to find one." << std::endl;
return true;
}
}
//
// If given an output file, we just append the output of each test suite
// we're asked to run to the end of that file. We need to append since the
// higher level test runner may be just running a number of tests back to
// back. We leave it up to that code to decide how to deal with possible
// parallel operation -- we just append to a file here. If no output file
// is specified, we don't do any output and just return the sense of error
// given by the test.
//
std::ofstream *pofs = 0;
std::ofstream ofs;
if (!outfileName.empty ())
{
ofs.open (outfileName.c_str (), std::fstream::out | std::fstream::app);
pofs = &ofs;
}
//
// If we have a specified test suite to run, then we only run that suite.
// The default case is to "run everything. We don't expect this to be done
// much since typically higher level code will be running suites in parallel
// but we'll do it if asked.
//
bool result = false;
bool suiteRan = false;
for (uint32_t i = 0; i < TestRunner::GetNTestSuites (); ++i)
{
TestSuite *testSuite = TestRunner::GetTestSuite (i);
if (doSuite == false || (doSuite == true && suiteName == testSuite->GetName ()))
{
testSuite->SetBaseDir (basedir);
testSuite->SetTempDir (tempdir);
testSuite->SetStream (pofs);
testSuite->SetVerbose (doVerbose);
testSuite->SetContinueOnFailure (doMultiple);
result |= testSuite->Run ();
suiteRan = true;
}
}
ofs.close();
//
// If we couldn't figure out how to run at least one test, then return an error
//
if (suiteRan == false)
{
std::cout << "Unable to find a test to run (constraints too severe or test not found)" << std::endl;
return true;
}
return result;
}