/* -*- 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 #include #include #include #include #include #include #include #include #include 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 ("/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; }