/* * Copyright (C) 2016-2018 Edward d'Auvergne * * This file is part of the program FlightGear. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 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, see . */ #include #include #include #include #include "dataStore.hxx" #include "fgTestRunner.hxx" #include "formatting.hxx" #include "logging.hxx" using namespace std; // The help message. void helpPrintout(std::ostream &stream) { stream << "Usage: fgfs_test_suite [options]\n"; stream << '\n'; stream << "Options:\n"; stream << " -h, --help show this help message and exit.\n"; stream << '\n'; stream << " Test selection options:\n"; stream << " -s, --system-tests execute the system/functional tests.\n"; stream << " -u, --unit-tests execute the unit tests.\n"; stream << " -g, --gui-tests execute the GUI tests.\n"; stream << " -m, --simgear-tests execute the simgear tests.\n"; stream << " -f, --fgdata-tests execute the FGData tests.\n"; stream << '\n'; stream << " The -s, -u, -g, and -m options accept an optional argument to perform a\n"; stream << " subset of all tests. This argument should either be the name of a test\n"; stream << " suite, the full name of an individual test, or a comma separated list.\n"; stream << '\n'; stream << " Full test names consist of the test suite name, the separator '::' and\n"; stream << " then the individual test name. The test names can revealed with the -t\n"; stream << " option.\n"; stream << '\n'; stream << " Examples:\n"; stream << " - perform and print the names of all available unit tests:\n"; stream << " --> fgfs_test_suite -t -u\n"; stream << " - run all tests from the NavRadioTests test suite:\n"; stream << " --> fgfs_test_suite -u NavRadioTests\n"; stream << " - run a specific test without discarding its output:\n"; stream << " --> fgfs_test_suite --no-summary -d -u NavRadioTests::testGS\n"; stream << '\n'; stream << " Logging options:\n"; stream << " --log-level={bulk,debug,info,warn,alert,popup,dev_warn,dev_alert}\n"; stream << " specify the minimum logging level to output\n"; stream << " --log-class=[none, terrain, astro, flight, input, gl, view, cockpit,\n"; stream << " general, math, event, aircraft, autopilot, io, clipper,\n"; stream << " network, atc, nasal, instrumentation, systems, ai, environment,\n"; stream << " sound, navaid, gui, terrasync, particles, headless, osg,\n"; stream << " undefined, all]\n"; stream << " select the logging class(es) to output.\n"; stream << " --log-split output the different non-interleaved log streams\n"; stream << " sequentially.\n"; stream << '\n'; stream << " Verbosity options:\n"; stream << " -t, --timings verbose output including names and timings for all\n"; stream << " tests.\n"; stream << " -c, --ctest simplified output suitable for running via CTest.\n"; stream << " -d, --debug disable IO capture for debugging (super verbose output).\n"; stream << " --no-summary disable the final summary printout.\n"; stream << '\n'; stream << " FG options:\n"; stream << " --fg-root the path to FGData.\n"; stream << '\n'; stream << "Environmental variables:\n"; stream << " FG_TEST_LOG_LEVEL equivalent to the --log-level option.\n"; stream << " FG_TEST_LOG_CLASS equivalent to the --log-class option.\n"; stream << " FG_TEST_LOG_SPLIT equivalent to the --log-split option.\n"; stream << " FG_TEST_TIMINGS equivalent to the -t or --timings option.\n"; stream << " FG_TEST_DEBUG equivalent to the -d or --debug option.\n"; stream << " FG_ROOT the path to FGData. The order of precedence is\n"; stream << " --fg-root, the FG_DATA_DIR CMake option, FG_ROOT,\n"; stream << " '../fgdata/', and '../data/'.\n"; stream.flush(); } // Convert the log class comma separated string list into a simgear debug class value. sgDebugClass processLogClass(std::string classList, bool &failure) { // Declarations. std::string logClassItem, val; unsigned int logClass=0; // Convert the command line value into a string array. std::replace(classList.begin(), classList.end(), ',', ' '); std::vector logClasses; stringstream temp(classList); while (temp >> val) logClasses.push_back(val); // Build up the value. for (auto const& logClassItem: logClasses) if (logClassItem == "none") logClass |= SG_NONE; else if (logClassItem == "terrain") logClass |= SG_TERRAIN; else if (logClassItem == "astro") logClass |= SG_ASTRO; else if (logClassItem == "flight") logClass |= SG_FLIGHT; else if (logClassItem == "input") logClass |= SG_INPUT; else if (logClassItem == "gl") logClass |= SG_GL; else if (logClassItem == "view") logClass |= SG_VIEW; else if (logClassItem == "cockpit") logClass |= SG_COCKPIT; else if (logClassItem == "general") logClass |= SG_GENERAL; else if (logClassItem == "math") logClass |= SG_MATH; else if (logClassItem == "event") logClass |= SG_EVENT; else if (logClassItem == "aircraft") logClass |= SG_AIRCRAFT; else if (logClassItem == "autopilot") logClass |= SG_AUTOPILOT; else if (logClassItem == "io") logClass |= SG_IO; else if (logClassItem == "clipper") logClass |= SG_CLIPPER; else if (logClassItem == "network") logClass |= SG_NETWORK; else if (logClassItem == "atc") logClass |= SG_ATC; else if (logClassItem == "nasal") logClass |= SG_NASAL; else if (logClassItem == "instrumentation") logClass |= SG_INSTR; else if (logClassItem == "systems") logClass |= SG_SYSTEMS; else if (logClassItem == "ai") logClass |= SG_AI; else if (logClassItem == "environment") logClass |= SG_ENVIRONMENT; else if (logClassItem == "sound") logClass |= SG_SOUND; else if (logClassItem == "navaid") logClass |= SG_NAVAID; else if (logClassItem == "gui") logClass |= SG_GUI; else if (logClassItem == "terrasync") logClass |= SG_TERRASYNC; else if (logClassItem == "particles") logClass |= SG_PARTICLES; else if (logClassItem == "headless") logClass |= SG_HEADLESS; else if (logClassItem == "osg") logClass |= SG_OSG; else if (logClassItem == "undefined") logClass |= SG_UNDEFD; else if (logClassItem == "all") logClass |= SG_ALL; else { std::cout << "The log class \"" << logClassItem << "\" must be one of:" << std::endl; std::cout << " {none, terrain, astro, flight, input, gl, view, cockpit, general, math," << std::endl; std::cout << " event, aircraft, autopilot, io, clipper, network, atc, nasal," << std::endl; std::cout << " instrumentation, systems, ai, environment, sound, navaid, gui, terrasync," << std::endl; std::cout << " particles, headless, osg, undefined, all}" << std::endl << std::endl; std::cout.flush(); failure = true; } // Return a simgear debug class. return sgDebugClass(logClass); } // Convert the log priority string into a simgear debug priority value. sgDebugPriority processLogPriority(std::string logLevel, bool &failure) { // Declarations. sgDebugPriority logPriority=SG_INFO; // Conversion. if (logLevel == "bulk") logPriority = SG_BULK; else if (logLevel == "debug") logPriority = SG_DEBUG; else if (logLevel == "info") logPriority = SG_INFO; else if (logLevel == "warn") logPriority = SG_WARN; else if (logLevel == "alert") logPriority = SG_ALERT; else if (logLevel == "popup") logPriority = SG_POPUP; else if (logLevel == "dev_warn") logPriority = SG_DEV_WARN; else if (logLevel == "dev_alert") logPriority = SG_DEV_ALERT; else { std::cout << "The log level setting of \"" << logLevel << "\" must be one of {bulk,debug,info,warn,alert,popup,dev_warn,dev_alert}.\n\n"; std::cout.flush(); failure = true; } // Return the simgear debug priority. return logPriority; } // Print out a summary of the relax test suite. void summary(CppUnit::OStream &stream, int system_result, int unit_result, int gui_result, int simgear_result, int fgdata_result) { int synopsis = 0; // Title. string text = "Summary of the FlightGear test suite"; printTitle(stream, text); // Subtitle. text = "Synopsis"; printSection(stream, text); // System/functional test summary. if (system_result != -1) { text = "System/functional tests"; printSummaryLine(stream, text, system_result); synopsis += system_result; } // Unit test summary. if (unit_result != -1) { text = "Unit tests"; printSummaryLine(stream, text, unit_result); synopsis += unit_result; } // GUI test summary. if (gui_result != -1) { text = "GUI tests"; printSummaryLine(stream, text, gui_result); synopsis += gui_result; } // Simgear unit test summary. if (simgear_result != -1) { text = "Simgear unit tests"; printSummaryLine(stream, text, simgear_result); synopsis += simgear_result; } // FGData test summary. if (fgdata_result != -1) { text = "FGData tests"; printSummaryLine(stream, text, fgdata_result); synopsis += fgdata_result; } // Synopsis. text ="Synopsis"; printSummaryLine(stream, text, synopsis); // End. stream << endl << endl; } int main(int argc, char **argv) { // Declarations. int status_gui=-1, status_simgear=-1, status_system=-1, status_unit=-1, status_fgdata=-1; bool run_system=false, run_unit=false, run_gui=false, run_simgear=false, run_fgdata=false; bool logSplit=false; bool timings=false, ctest_output=false, debug=false, printSummary=true, help=false; char *subset_system=NULL, *subset_unit=NULL, *subset_gui=NULL, *subset_simgear=NULL, *subset_fgdata=NULL; bool failure=false; char firstchar; std::string arg, delim, fgRoot, logClassVal, logLevel; size_t delimPos; // The default logging class and priority to show. sgDebugClass logClass=SG_ALL; sgDebugPriority logPriority=SG_INFO; // Process environmental variables before the command line options. if (getenv("FG_TEST_LOG_LEVEL")) logPriority = processLogPriority(getenv("FG_TEST_LOG_LEVEL"), failure); if (getenv("FG_TEST_LOG_CLASS")) logClass = processLogClass(getenv("FG_TEST_LOG_CLASS"), failure); if (getenv("FG_TEST_LOG_SPLIT")) logSplit = true; if (getenv("FG_TEST_TIMINGS")) timings = true; if (getenv("FG_TEST_DEBUG")) debug = true; if (failure) return 1; // Argument parsing. for (int i = 1; i < argc; i++) { firstchar = '\0'; arg = argv[i]; if (i < argc-1) firstchar = argv[i+1][0]; // System test. if (arg == "-s" || arg == "--system-tests") { run_system = true; if (firstchar != '-') subset_system = argv[i+1]; // Unit test. } else if (arg == "-u" || arg == "--unit-tests") { run_unit = true; if (firstchar != '-') subset_unit = argv[i+1]; // GUI test. } else if (arg == "-g" || arg == "--gui-tests") { run_gui = true; if (firstchar != '-') subset_gui = argv[i+1]; // Simgear test. } else if (arg == "-m" || arg == "--simgear-tests") { run_simgear = true; if (firstchar != '-') subset_simgear = argv[i+1]; // FGData test. } else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--fgdata-tests") == 0) { run_fgdata = true; if (firstchar != '-') subset_fgdata = argv[i+1]; // Log class. } else if (arg.find( "--log-class" ) == 0) { // Process the command line. logClass = SG_NONE; delimPos = arg.find('='); logClassVal = arg.substr(delimPos + 1); logClass = processLogClass(logClassVal, failure); if (failure) return 1; // Log level. } else if (arg.find( "--log-level" ) == 0) { // Process the command line level. delimPos = arg.find('='); logLevel = arg.substr(delimPos + 1); logPriority = processLogPriority(logLevel, failure); if (failure) return 1; // Log splitting. } else if (arg == "--log-split") { logSplit = true; // Timing output. } else if (arg == "-t" || arg == "--timings") { timings = true; // CTest suitable output. } else if (arg == "-c" || arg == "--ctest") { ctest_output = true; // Debug output. } else if (arg == "-d" || arg == "--debug") { debug = true; // No summary output. } else if (arg == "--no-summary") { printSummary = false; // Help. } else if (arg == "-h" || arg == "--help") { help = true; // FGData path. } else if (arg == "--fg-root") { if (firstchar != '-') fgRoot = argv[i+1]; } } // Help. if (help) { helpPrintout(std::cout); return 0; } // Turn on all tests if no subset was specified. if (!run_system && !run_unit && !run_gui && !run_simgear && !run_fgdata) { run_system = true; run_unit = true; run_gui = true; run_simgear = true; run_fgdata = true; } // Set up the data store singleton and FGData path. DataStore& data = DataStore::get(); if (data.findFGRoot(fgRoot, debug) != 0) { return 1; } if (data.validateFGRoot() != 0) { return 1; } // Set up logging. sglog().setDeveloperMode(true); if (debug) sglog().setLogLevels(sgDebugClass(logClass), logPriority); else setupLogging(sgDebugClass(logClass), logPriority, logSplit); // Execute each of the test suite categories. if (run_system) status_system = testRunner("System tests", "System / functional tests", subset_system, timings, ctest_output, debug); if (run_unit) status_unit = testRunner("Unit tests", "Unit tests", subset_unit, timings, ctest_output, debug); if (run_gui && 0) // Disabled as there are no GUI tests yet. status_gui = testRunner("GUI tests", "GUI tests", subset_gui, timings, ctest_output, debug); if (run_simgear) status_simgear = testRunner("Simgear unit tests", "Simgear unit tests", subset_simgear, timings, ctest_output, debug); if (run_fgdata) status_fgdata = testRunner("FGData tests", "FGData tests", subset_fgdata, timings, ctest_output, debug); // Summary printout. if (printSummary && !ctest_output) summary(cerr, status_system, status_unit, status_gui, status_simgear, status_fgdata); // Deactivate the logging. if (!debug) stopLogging(); // Failure. if (status_system > 0) return 1; if (status_unit > 0) return 1; if (status_gui > 0) return 1; if (status_simgear > 0) return 1; if (status_fgdata > 0) return 1; // Success. return 0; }