From 70d11fe983c2129243c856770649767cc8b5aa43 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 10 Jan 2022 22:36:50 -0300 Subject: [PATCH] build: ns3 and CMake fixes - fix scratch shortcut transformation to build target and binary - scan scratch files for a main function to use them as the target name - fix CMake generator parsing - update the -- separator message - use -- separator to forward CMake flags from ns3 - add --vis option to ns3 - embedded version fixes Fix NS3_VERSION_PATCH and NS3_VERSION_RELEASE_CANDIDATE default values. Add check-version target to CMake. Add --enable-build-version to ns3. Add --check-profile and --check-version options to ns3. - process each scratch subdirectory as a single target - forward ns3 arguments after -- separator to the program to run - fix escape sequence in command-template help string - handle modules with very long names --- .../custom_modules/ns3_module_macros.cmake | 5 + .../custom_modules/ns3_versioning.cmake | 83 +++++++++ .../waf_workaround_fakeconfig.cmake | 13 ++ buildsupport/macros_and_definitions.cmake | 64 +------ ns3 | 97 ++++++++--- scratch/CMakeLists.txt | 92 +++++++--- utils/tests/test-ns3.py | 160 +++++++++++++++++- 7 files changed, 399 insertions(+), 115 deletions(-) diff --git a/buildsupport/custom_modules/ns3_module_macros.cmake b/buildsupport/custom_modules/ns3_module_macros.cmake index 8fc2ab642..cf72c439d 100644 --- a/buildsupport/custom_modules/ns3_module_macros.cmake +++ b/buildsupport/custom_modules/ns3_module_macros.cmake @@ -85,6 +85,11 @@ macro( set(config_headers ${CMAKE_HEADER_OUTPUT_DIRECTORY}/config-store-config.h ${CMAKE_HEADER_OUTPUT_DIRECTORY}/core-config.h ) + if(${NS3_ENABLE_BUILD_VERSION}) + list(APPEND config_headers + ${CMAKE_HEADER_OUTPUT_DIRECTORY}/version-defines.h + ) + endif() endif() set_target_properties( ${lib${libname}} diff --git a/buildsupport/custom_modules/ns3_versioning.cmake b/buildsupport/custom_modules/ns3_versioning.cmake index 51a4c3a51..fd2cfbdf5 100644 --- a/buildsupport/custom_modules/ns3_versioning.cmake +++ b/buildsupport/custom_modules/ns3_versioning.cmake @@ -64,3 +64,86 @@ function(check_ns3_closest_tags CLOSEST_TAG VERSION_TAG_DISTANCE set(${VERSION_DIRTY_FLAG} 1 PARENT_SCOPE) endif() endfunction() + +function(configure_embedded_version) + find_program(GIT git) + if(${NS3_ENABLE_BUILD_VERSION} AND (NOT GIT)) + message(FATAL_ERROR "Embedding build version into libraries require Git.") + endif() + + # Check version target will not be created + if(NOT GIT) + message( + STATUS "Git was not found. Version related targets won't be enabled" + ) + return() + endif() + + check_git_repo_has_ns3_tags(HAS_NS3_TAGS NS3_VERSION_TAG) + + if(NOT ${HAS_NS3_TAGS}) + message( + FATAL_ERROR + "This repository doesn't contain ns-3 git tags to bake into the libraries." + ) + endif() + + check_ns3_closest_tags( + NS3_VERSION_CLOSEST_TAG NS3_VERSION_TAG_DISTANCE NS3_VERSION_COMMIT_HASH + NS3_VERSION_DIRTY_FLAG + ) + set(DIRTY) + if(${NS3_VERSION_DIRTY_FLAG}) + set(DIRTY "-dirty") + endif() + + set(version + ${NS3_VERSION_TAG}+${NS3_VERSION_TAG_DISTANCE}@${NS3_VERSION_COMMIT_HASH}${DIRTY}-${build_profile} + ) + add_custom_target(check-version COMMAND echo ns-3 version: ${version}) + + # Split commit tag (ns-3.[.patch][-RC]) into + # (ns;3.[.patch];[-RC]): + string(REPLACE "-" ";" NS3_VER_LIST ${NS3_VERSION_TAG}) + list(LENGTH NS3_VER_LIST NS3_VER_LIST_LEN) + + # Get last version tag fragment (RC) + set(RELEASE_CANDIDATE " ") + if(${NS3_VER_LIST_LEN} GREATER 2) + list(GET NS3_VER_LIST 2 RELEASE_CANDIDATE) + endif() + + # Get 3.[.patch] + list(GET NS3_VER_LIST 1 VERSION_STRING) + # Split into a list 3;[;patch] + string(REPLACE "." ";" VERSION_LIST ${VERSION_STRING}) + list(LENGTH VERSION_LIST VER_LIST_LEN) + + list(GET VERSION_LIST 0 NS3_VERSION_MAJOR) + if(${VER_LIST_LEN} GREATER 1) + list(GET VERSION_LIST 1 NS3_VERSION_MINOR) + if(${VER_LIST_LEN} GREATER 2) + list(GET VERSION_LIST 2 NS3_VERSION_PATCH) + else() + set(NS3_VERSION_PATCH "00") + endif() + endif() + + # Transform list with 1 entry into strings + set(NS3_VERSION_MAJOR "${NS3_VERSION_MAJOR}") + set(NS3_VERSION_MINOR "${NS3_VERSION_MINOR}") + set(NS3_VERSION_PATCH "${NS3_VERSION_PATCH}") + set(NS3_VERSION_TAG "${NS3_VERSION_TAG}") + set(NS3_VERSION_RELEASE_CANDIDATE "${RELEASE_CANDIDATE}") + set(NS3_VERSION_BUILD_PROFILE ${cmakeBuildType}) + + # Enable embedding build version + if(${NS3_ENABLE_BUILD_VERSION}) + add_definitions(-DENABLE_BUILD_VERSION=1) + configure_file( + buildsupport/version-defines-template.h + ${CMAKE_HEADER_OUTPUT_DIRECTORY}/version-defines.h + ) + endif() + +endfunction() diff --git a/buildsupport/custom_modules/waf_workaround_fakeconfig.cmake b/buildsupport/custom_modules/waf_workaround_fakeconfig.cmake index ccfeb5fb2..f9514e652 100644 --- a/buildsupport/custom_modules/waf_workaround_fakeconfig.cmake +++ b/buildsupport/custom_modules/waf_workaround_fakeconfig.cmake @@ -74,10 +74,17 @@ function(print_formatted_table_with_modules table_name modules output) set(width 26) # Variable with column width string(REPLACE "lib" "" modules_to_print "${modules}") list(SORT modules_to_print) # Sort for nice output + set(modules_with_large_names) foreach(module ${modules_to_print}) # Get the size of the module string name string(LENGTH ${module} module_name_length) + # Skip modules with names wider than 26 characters + if(${module_name_length} GREATER_EQUAL ${width}) + list(APPEND modules_with_large_names ${module}) + continue() + endif() + # Calculate trailing spaces to fill the column math(EXPR num_trailing_spaces "${width} - ${module_name_length}") @@ -94,7 +101,13 @@ function(print_formatted_table_with_modules table_name modules output) set(count 0) endif() endforeach() + + # Print modules with large names one by one + foreach(module ${modules_with_large_names}) + string(APPEND temp "${module}\n") + endforeach() string(APPEND temp "\n") + # Save the table outer scope out variable set(${output} ${${output}}${temp} PARENT_SCOPE) endfunction() diff --git a/buildsupport/macros_and_definitions.cmake b/buildsupport/macros_and_definitions.cmake index 9ddd94fcf..628c4bf8a 100644 --- a/buildsupport/macros_and_definitions.cmake +++ b/buildsupport/macros_and_definitions.cmake @@ -326,68 +326,8 @@ macro(process_options) endif() endif() - if(${NS3_ENABLE_BUILD_VERSION}) - include(buildsupport/custom_modules/ns3_versioning.cmake) - add_definitions(-DENABLE_BUILD_VERSION=1) - - find_program(GIT git) - if(NOT GIT) - message(FATAL_ERROR "Baking build version into libraries require Git.") - endif() - - check_git_repo_has_ns3_tags(HAS_NS3_TAGS NS3_VERSION_TAG) - - if(NOT ${HAS_NS3_TAGS}) - message( - FATAL_ERROR - "This repository doesn't contain ns-3 git tags to bake into the libraries." - ) - endif() - - check_ns3_closest_tags( - NS3_VERSION_CLOSEST_TAG NS3_VERSION_TAG_DISTANCE NS3_VERSION_COMMIT_HASH - NS3_VERSION_DIRTY_FLAG - ) - - # Split commit tag (ns-3.[.patch][-RC]) into - # (ns;3.[.patch];[-RC]): - string(REPLACE "-" ";" NS3_VER_LIST ${NS3_VERSION_TAG}) - list(LENGTH NS3_VER_LIST NS3_VER_LIST_LEN) - - # Get last version tag fragment (RC) - set(NS3_VERSION_RELEASE_CANDIDATE) - if(${NS3_VER_LIST_LEN} GREATER 2) - list(GET NS3_VER_LIST 2 RELEASE_CANDIDATE) - set(NS3_VERSION_RELEASE_CANDIDATE "${RELEASE_CANDIDATE}") - endif() - - # Get 3.[.patch] - list(GET NS3_VER_LIST 1 VERSION_STRING) - # Split into a list 3;[;patch] - string(REPLACE "." ";" VERSION_LIST ${VERSION_STRING}) - list(LENGTH VERSION_LIST VER_LIST_LEN) - - list(GET VERSION_LIST 0 NS3_VERSION_MAJOR) - if(${VER_LIST_LEN} GREATER 1) - list(GET VERSION_LIST 1 NS3_VERSION_MINOR) - if(${VER_LIST_LEN} GREATER 2) - list(GET VERSION_LIST 2 NS3_VERSION_PATCH) - endif() - endif() - - # Transform list with 1 entry into strings - set(NS3_VERSION_MAJOR "${NS3_VERSION_MAJOR}") - set(NS3_VERSION_MINOR "${NS3_VERSION_MINOR}") - set(NS3_VERSION_PATCH "${NS3_VERSION_PATCH}") - set(NS3_VERSION_TAG "${NS3_VERSION_TAG}") - - # Set - set(NS3_VERSION_BUILD_PROFILE ${cmakeBuildType}) - configure_file( - buildsupport/version-defines-template.h - ${CMAKE_HEADER_OUTPUT_DIRECTORY}/version-defines.h - ) - endif() + include(buildsupport/custom_modules/ns3_versioning.cmake) + configure_embedded_version() if(${NS3_CLANG_FORMAT}) find_program(CLANG_FORMAT clang-format) diff --git a/ns3 b/ns3 index 931d29b41..eb4302a26 100755 --- a/ns3 +++ b/ns3 @@ -72,8 +72,7 @@ def parse_args(argv): parser_configure = sub_parser.add_parser('configure', help='Try "./ns3 configure --help" for more configuration options') parser_configure.add_argument('configure', - nargs='?', - action='store', default=True) + action='store_true', default=False) parser_configure.add_argument('-d', '--build-profile', help='Build profile', dest='build_profile', @@ -93,6 +92,8 @@ def parse_args(argv): parser_configure = on_off_argument(parser_configure, "des-metrics", "Logging all events in a json file with the name of the executable " "(which must call CommandLine::Parse(argc, argv)") + parser_configure = on_off_argument(parser_configure, "build-version", + "embedding git changes as a build version during build") parser_configure = on_off_argument(parser_configure, "examples", "the ns-3 examples") parser_configure = on_off_argument(parser_configure, "gcov", "code coverage analysis") parser_configure = on_off_argument(parser_configure, "gtk", "GTK support in ConfigStore") @@ -151,21 +152,16 @@ def parse_args(argv): action="store_true", default=None, dest="configure_dry_run") parser_clean = sub_parser.add_parser('clean', help='Removes files created by waf and ns3') - parser_clean.add_argument('clean', - nargs="?", - action="store", default=True) + parser_clean.add_argument('clean', action="store_true", default=False) parser_clean.add_argument('--dry-run', help="Do not execute the commands", action="store_true", default=None, dest="clean_dry_run") parser_install = sub_parser.add_parser('install', help='Install ns-3') - parser_install.add_argument('install', - nargs="?", - action="store", default=True) + parser_install.add_argument('install', action="store_true", default=False) + parser_uninstall = sub_parser.add_parser('uninstall', help='Uninstall ns-3') - parser_uninstall.add_argument('uninstall', - nargs="?", - action="store", default=True) + parser_uninstall.add_argument('uninstall', action="store_true", default=False) parser_run = sub_parser.add_parser('run', help='Try "./ns3 run --help" for more runtime options') @@ -177,7 +173,7 @@ def parse_args(argv): action="store_true", default=False) parser_run.add_argument('--command-template', help=('Template of the command used to run the program given by run;' - ' It should be a shell command string containing %s inside,' + ' It should be a shell command string containing %%s inside,' ' which will be replaced by the actual program.'), type=str, default=None) parser_run.add_argument('--cwd', @@ -189,9 +185,9 @@ def parse_args(argv): parser_run.add_argument('--valgrind', help='Change the default command template to run programs with valgrind', action="store_true", default=None) - parser_run.add_argument('--visualize', + parser_run.add_argument('--vis', '--visualize', help='Modify --run arguments to enable the visualizer', - action="store_true", default=None) + action="store_true", dest="visualize", default=None) parser_run.add_argument('--dry-run', help="Do not execute the commands", action="store_true", default=None, dest="run_dry_run") @@ -203,9 +199,8 @@ def parse_args(argv): parser_shell = sub_parser.add_parser('shell', help='Try "./ns3 shell --help" for more runtime options') parser_shell.add_argument('shell', - nargs="?", help='Export necessary environment variables and open a shell', - action="store", default=True) + action="store_true", default=False) parser_docs = sub_parser.add_parser('docs', help='Try "./ns3 docs --help" for more documentation options') @@ -231,6 +226,12 @@ def parse_args(argv): parser.add_argument('--check', help='DEPRECATED (run ./test.py)', action='store_true', default=None) + parser.add_argument('--check-profile', + help='Print out current build profile', + action='store_true', default=None) + parser.add_argument('--check-version', + help='Print the current build version', + action='store_true', default=None) # parser.add_argument('--docset', # help=( @@ -239,7 +240,8 @@ def parse_args(argv): # action="store_true", default=None, # dest="docset_build") - args = parser.parse_args(argv) + # Parse known arguments and separate from unknown arguments + args, unknown_args = parser.parse_known_args(argv) # Merge dry_runs dry_run_args = [(args.__getattribute__(name) if name in args else None) for name in @@ -253,6 +255,23 @@ def parse_args(argv): if args.run and args.enable_sudo is None: args.enable_sudo = True + + # Filter arguments before -- + setattr(args, "program_args", []) + if unknown_args: + try: + args_separator_index = argv.index('--') + args.program_args = argv[args_separator_index + 1:] + except ValueError: + msg = "Unknown options were given: {options}.\n"\ + "To see the allowed options add the `--help` option.\n"\ + "To forward configuration or runtime options, put them after '--'.\n" + if args.run: + msg += "Try: ./ns3 run {target} -- {options}\n" + if args.configure: + msg += "Try: ./ns3 configure -- {options}\n" + msg = msg.format(options=", ".join(unknown_args), target=args.run) + raise Exception(msg) return args @@ -337,7 +356,7 @@ def search_cmake_cache(build_profile): current_cmake_cache_folder = os.path.dirname(cmake_cache_file) # Check the generator - if "CMAKE_GENERATOR" in line: + if "CMAKE_GENERATOR:" in line: current_cmake_generator = line.split("=")[-1] if not current_cmake_generator: @@ -360,12 +379,17 @@ def search_cmake_cache(build_profile): return current_cmake_cache_folder, current_cmake_generator +def project_not_configured(config_msg=""): + print("You need to configure ns-3 first: try ./ns3 configure%s" % config_msg) + exit(1) + + def check_config(current_cmake_cache_folder): if current_cmake_cache_folder is None: - raise Exception("Project was not configured") + project_not_configured() waf_like_config_table = current_cmake_cache_folder + os.sep + "ns3wafconfig.txt" if not os.path.exists(waf_like_config_table): - raise Exception("Project was not configured") + project_not_configured() with open(waf_like_config_table, "r") as f: print(f.read()) @@ -419,6 +443,7 @@ def configure_cmake(cmake, args, current_cmake_cache_folder, current_cmake_gener options = (("ASSERT", "asserts"), ("COVERAGE", "gcov"), ("DES_METRICS", "des_metrics"), + ("ENABLE_BUILD_VERSION", "build_version"), ("ENABLE_SUDO", "sudo"), ("EXAMPLES", "examples"), ("GTK3", "gtk"), @@ -468,6 +493,9 @@ def configure_cmake(cmake, args, current_cmake_cache_folder, current_cmake_gener cmake_args.append("-G") cmake_args.append(args.G) + # Append CMake flags passed using the -- separator + cmake_args.extend(args.program_args) + # Configure cmake cmake_args.append("..") # for now, assuming the cmake_cache directory is inside the ns-3-dev folder @@ -502,6 +530,11 @@ def get_program_shortcuts(build_profile, ns3_version): # Remove version prefix and build type suffix from shortcuts (or keep them too?) temp_path[-1] = temp_path[-1].replace("-" + build_profile, "").replace("ns" + ns3_version + "-", "") + # Deal with scratch subdirs + if "scratch" in temp_path and len(temp_path) > 3: + subdir = "_".join([*temp_path[2:-1], ""]) + temp_path[-1] = temp_path[-1].replace(subdir, "") + # Check if there is a .cc file for that specific program source_file_path = os.sep.join(temp_path) + ".cc" source_shortcut = False @@ -611,7 +644,7 @@ def get_target_to_build(program_path, ns3_version, build_profile): if "scratch" in program_path: # Get the path to the program and replace slashes with underlines # to get unique targets for CMake, preventing collisions with modules examples - return program_name.split(out_dir + "/")[1].replace("/", "_") + return program_name.replace(out_dir, "").replace("/", "_")[1:] else: # Other programs just use their normal names (without version prefix and build_profile suffix) as targets return program_name.split("/")[-1] @@ -661,7 +694,9 @@ def build_step(args, # If we are building specific targets, we build them one by one if "build" in args: - non_executable_targets = ["docs", + non_executable_targets = ["check-version", + "cmake-format", + "docs", "doxygen", "doxygen-no-build", "sphinx", @@ -670,7 +705,7 @@ def build_step(args, "tutorial", "install", "uninstall", - "cmake-format"] + ] # Build targets in the list for target in args.build: if target in ns3_modules: @@ -723,6 +758,7 @@ def run_step(args, target_to_run, target_args): debugging_software = [] working_dir = ns3_path use_shell = False + target_args += args.program_args # running test.py/check? if args.check: @@ -893,6 +929,15 @@ def main(): # Get build profile build_profile, ns3_version, ns3_modules, enable_sudo = check_build_profile(out_dir) + if args.check_profile: + if build_profile: + print("Build profile: %s" % build_profile) + else: + project_not_configured() + + if args.check_version: + args.build = ["check-version"] + # Check if running something or reconfiguring ns-3 run_only = False build_and_run = False @@ -934,8 +979,10 @@ def main(): ) if not project_configured(current_cmake_cache_folder): - print("You need to configure ns-3 first: try ./ns3 configure") - exit(0) + project_not_configured() + + if ns3_modules is None: + project_not_configured() # We could also replace the "ns3-" prefix used in c4che with the "lib" prefix currently used in cmake ns3_modules = [module.replace("ns3-", "") for module in ns3_modules] diff --git a/scratch/CMakeLists.txt b/scratch/CMakeLists.txt index 182b34d94..4b4476a61 100644 --- a/scratch/CMakeLists.txt +++ b/scratch/CMakeLists.txt @@ -1,36 +1,78 @@ -file(GLOB_RECURSE scratches ${CMAKE_CURRENT_SOURCE_DIR}/*.cc) - -set(DONT_BUILD) set(target_prefix scratch_) -foreach(scratch_src ${scratches}) - # Get source filename without path or extension - get_filename_component(scratch_name ${scratch_src} NAME) - string(REGEX REPLACE "\\.[^.]*$" "" scratch_name ${scratch_name}) +function(create_scratch source_files) + # Return early if no sources in the subdirectory + list(LENGTH source_files number_sources) + if(number_sources EQUAL 0) + return() + endif() + + # If the scratch has more than a source file, we need + # to find the source with the main function + unset(scratch_src) + foreach(source_file ${source_files}) + file(READ ${source_file} source_file_contents) + string(REGEX MATCHALL "main[(| (]" main_position "${source_file_contents}") + if(CMAKE_MATCH_0) + set(scratch_src ${source_file}) + endif() + endforeach() + + # Get parent directory name + get_filename_component(scratch_dirname ${scratch_src} DIRECTORY) + string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" scratch_dirname + ${scratch_dirname} + ) + # Get source name + get_filename_component(scratch_name ${scratch_src} NAME_WE) + + set(target_prefix scratch_) + if(${scratch_dirname}) + # Join the names together if dirname is not the scratch folder + set(target_prefix scratch_${scratch_dirname}_) + endif() # Get source absolute path and transform into relative path get_filename_component(scratch_absolute_directory ${scratch_src} DIRECTORY) string(REPLACE "${PROJECT_SOURCE_DIR}" "${CMAKE_OUTPUT_DIRECTORY}" scratch_directory ${scratch_absolute_directory} ) - - # Build scratch if not listed as a DONT_BUILD - string(FIND "${DONT_BUILD}" "${scratch_name}" res) - if(res LESS 0) - add_executable(${target_prefix}${scratch_name} "${scratch_src}") - if(${NS3_STATIC}) - target_link_libraries( - ${target_prefix}${scratch_name} ${LIB_AS_NEEDED_PRE_STATIC} - ${lib-ns3-static} - ) - else() - target_link_libraries( - ${target_prefix}${scratch_name} "${ns3-libs}" "${ns3-contrib-libs}" - "${ns3-external-libs}" - ) - endif() - set_runtime_outputdirectory( - ${scratch_name} ${scratch_directory}/ ${target_prefix} + add_executable(${target_prefix}${scratch_name} "${source_files}") + if(${NS3_STATIC}) + target_link_libraries( + ${target_prefix}${scratch_name} ${LIB_AS_NEEDED_PRE_STATIC} + ${lib-ns3-static} + ) + else() + target_link_libraries( + ${target_prefix}${scratch_name} "${ns3-libs}" "${ns3-contrib-libs}" + "${ns3-external-libs}" ) endif() + set_runtime_outputdirectory( + ${scratch_name} ${scratch_directory}/ ${target_prefix} + ) +endfunction() + +# Scan *.cc files in ns-3-dev/scratch and build a target for each +file(GLOB single_source_file_scratches ${CMAKE_CURRENT_SOURCE_DIR}/*.cc) +foreach(scratch_src ${single_source_file_scratches}) + create_scratch(${scratch_src}) +endforeach() + +# Scan *.cc files in ns-3-dev/scratch subdirectories and build a target for each +# subdirectory +file(GLOB_RECURSE scratch_subdirectories LIST_DIRECTORIES true + ${CMAKE_CURRENT_SOURCE_DIR}/** +) +# Filter out files +foreach(entry ${scratch_subdirectories}) + if(NOT (IS_DIRECTORY ${entry})) + list(REMOVE_ITEM scratch_subdirectories ${entry}) + endif() +endforeach() + +foreach(subdir ${scratch_subdirectories}) + file(GLOB scratch_sources ${subdir}/*.cc) + create_scratch("${scratch_sources}") endforeach() diff --git a/utils/tests/test-ns3.py b/utils/tests/test-ns3.py index 6a9251955..c81ec0e01 100644 --- a/utils/tests/test-ns3.py +++ b/utils/tests/test-ns3.py @@ -288,7 +288,7 @@ class NS3CommonSettingsTestCase(unittest.TestCase): @return None """ return_code, stdout, stderr = run_ns3("") - self.assertEqual(return_code, 0) + self.assertEqual(return_code, 1) self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout) def test_02_NoTaskLines(self): @@ -297,7 +297,7 @@ class NS3CommonSettingsTestCase(unittest.TestCase): @return None """ return_code, stdout, stderr = run_ns3("--quiet") - self.assertEqual(return_code, 0) + self.assertEqual(return_code, 1) self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout) def test_03_CheckConfig(self): @@ -307,7 +307,25 @@ class NS3CommonSettingsTestCase(unittest.TestCase): """ return_code, stdout, stderr = run_ns3("--check-config") self.assertEqual(return_code, 1) - self.assertIn("Project was not configured", stderr) + self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout) + + def test_04_CheckProfile(self): + """! + Test only passing --check-profile argument to ns3 + @return None + """ + return_code, stdout, stderr = run_ns3("--check-profile") + self.assertEqual(return_code, 1) + self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout) + + def test_05_CheckVersion(self): + """! + Test only passing --check-version argument to ns3 + @return None + """ + return_code, stdout, stderr = run_ns3("--check-version") + self.assertEqual(return_code, 1) + self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout) class NS3ConfigureBuildProfileTestCase(unittest.TestCase): @@ -734,6 +752,33 @@ class NS3ConfigureTestCase(NS3BaseTestCase): ) self.assertNotEqual(return_code, 0) + def test_10_CheckConfig(self): + """! + Test passing --check-config argument to ns3 to get the configuration table + @return None + """ + return_code, stdout, stderr = run_ns3("--check-config") + self.assertEqual(return_code, 0) + self.assertIn("Summary of optional NS-3 features", stdout) + + def test_11_CheckProfile(self): + """! + Test passing --check-profile argument to ns3 to get the build profile + @return None + """ + return_code, stdout, stderr = run_ns3("--check-profile") + self.assertEqual(return_code, 0) + self.assertIn("Build profile: debug", stdout) + + def test_12_CheckVersion(self): + """! + Test passing --check-version argument to ns3 to get the build version + @return None + """ + return_code, stdout, stderr = run_ns3("--check-version") + self.assertEqual(return_code, 0) + self.assertIn("ns-3 version:", stdout) + class NS3BuildBaseTestCase(NS3BaseTestCase): """! @@ -1047,6 +1092,32 @@ class NS3BuildBaseTestCase(NS3BaseTestCase): # Reset flag to let it clean the build NS3BuildBaseTestCase.cleaned_once = False + def test_09_Scratches(self): + """! + Tries to build scratch-simulator and subdir/scratch-simulator-subdir + @return None + """ + # Build. + targets = {"scratch/scratch-simulator": "scratch-simulator", + "scratch-simulator": "scratch-simulator", + "scratch/subdir/scratch-simulator-subdir": "subdir_scratch-simulator-subdir", + "subdir/scratch-simulator-subdir": "subdir_scratch-simulator-subdir", + "scratch-simulator-subdir": "subdir_scratch-simulator-subdir", + } + for (target_to_run, target_cmake) in targets.items(): + # Test if build is working. + build_line = "target scratch_%s" % target_cmake + return_code, stdout, stderr = run_ns3("build %s" % target_to_run) + self.assertEqual(return_code, 0) + self.assertIn(build_line, stdout) + + # Test if run is working + return_code, stdout, stderr = run_ns3("run %s" % target_to_run) + self.assertEqual(return_code, 0) + self.assertIn(build_line, stdout) + stdout = stdout.replace("scratch_%s" % target_cmake, "") # remove build lines + self.assertIn(target_to_run.split("/")[-1], stdout) + class NS3ExpectedUseTestCase(NS3BaseTestCase): """! @@ -1354,6 +1425,89 @@ class NS3ExpectedUseTestCase(NS3BaseTestCase): self.assertEqual(fstat.st_uid, 0) # check the file was correctly chown'ed by root self.assertEqual(fstat.st_mode & stat.S_ISUID, stat.S_ISUID) # check if normal users can run as sudo + def test_16_CommandTemplate(self): + """! + Check if command template is working + @return None + """ + + # Command templates that are empty or do not have a %s should fail + return_code0, stdout0, stderr0 = run_ns3('run sample-simulator --command-template') + self.assertEqual(return_code0, 2) + self.assertIn("argument --command-template: expected one argument", stderr0) + + return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template=" "') + return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --command-template " "') + return_code3, stdout3, stderr3 = run_ns3('run sample-simulator --command-template "echo "') + self.assertEqual((return_code1, return_code2, return_code3), (1, 1, 1)) + self.assertIn("not all arguments converted during string formatting", stderr1) + self.assertEqual(stderr1, stderr2) + self.assertEqual(stderr2, stderr3) + + # Command templates with %s should at least continue and try to run the target + return_code4, stdout4, stderr4 = run_ns3('run sample-simulator --command-template "%s --PrintVersion"') + return_code5, stdout5, stderr5 = run_ns3('run sample-simulator --command-template="%s --PrintVersion"') + self.assertEqual((return_code4, return_code5), (0, 0)) + self.assertIn("sample-simulator --PrintVersion", stdout4) + self.assertIn("sample-simulator --PrintVersion", stdout5) + + def test_17_ForwardArgumentsToRunTargets(self): + """! + Check if all flavors of different argument passing to + executable targets are working + @return None + """ + + # Test if all argument passing flavors are working + return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help"') + return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template="%s --help"') + return_code2, stdout2, stderr2 = run_ns3('run sample-simulator -- --help') + + self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0)) + self.assertIn("sample-simulator --help", stdout0) + self.assertIn("sample-simulator --help", stdout1) + self.assertIn("sample-simulator --help", stdout2) + + # Test if the same thing happens with an additional run argument (e.g. --no-build) + return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help" --no-build') + return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template="%s --help" --no-build') + return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --no-build -- --help') + self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0)) + self.assertEqual(stdout0, stdout1) + self.assertEqual(stdout1, stdout2) + self.assertEqual(stderr0, stderr1) + self.assertEqual(stderr1, stderr2) + + # Now collect results for each argument individually + return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --PrintGlobals"') + return_code1, stdout1, stderr1 = run_ns3('run "sample-simulator --PrintGroups"') + return_code2, stdout2, stderr2 = run_ns3('run "sample-simulator --PrintTypeIds"') + + self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0)) + self.assertIn("sample-simulator --PrintGlobals", stdout0) + self.assertIn("sample-simulator --PrintGroups", stdout1) + self.assertIn("sample-simulator --PrintTypeIds", stdout2) + + # Then check if all the arguments are correctly merged by checking the outputs + cmd = 'run "sample-simulator --PrintGlobals" --command-template="%s --PrintGroups" -- --PrintTypeIds' + return_code, stdout, stderr = run_ns3(cmd) + self.assertEqual(return_code, 0) + + # The order of the arguments is command template, + # arguments passed with the target itself + # and forwarded arguments after the -- separator + self.assertIn("sample-simulator --PrintGroups --PrintGlobals --PrintTypeIds", stdout) + + # Check if it complains about the missing -- separator + cmd0 = 'run sample-simulator --command-template="%s " --PrintTypeIds' + cmd1 = 'run sample-simulator --PrintTypeIds' + + return_code0, stdout0, stderr0 = run_ns3(cmd0) + return_code1, stdout1, stderr1 = run_ns3(cmd1) + self.assertEqual((return_code0, return_code1), (1, 1)) + self.assertIn("To forward configuration or runtime options, put them after '--'", stderr0) + self.assertIn("To forward configuration or runtime options, put them after '--'", stderr1) + if __name__ == '__main__': loader = unittest.TestLoader()