A Discrete-Event Network Simulator
API
test-ns3.py
Go to the documentation of this file.
1 #! /usr/bin/env python3
2 #
3 # Copyright (c) 2021-2023 Universidade de Brasília
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License version 2 as
7 # published by the Free Software Foundation;
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 #
18 # Author: Gabriel Ferreira <gabrielcarvfer@gmail.com>
19 
20 """!
21 Test suite for the ns3 wrapper script
22 """
23 
24 import glob
25 import os
26 import re
27 import shutil
28 import subprocess
29 import sys
30 import unittest
31 from functools import partial
32 
33 # Get path containing ns3
34 ns3_path = os.path.dirname(os.path.abspath(os.sep.join([__file__, "../../"])))
35 ns3_lock_filename = os.path.join(ns3_path, ".lock-ns3_%s_build" % sys.platform)
36 ns3_script = os.sep.join([ns3_path, "ns3"])
37 ns3rc_script = os.sep.join([ns3_path, ".ns3rc"])
38 usual_outdir = os.sep.join([ns3_path, "build"])
39 usual_lib_outdir = os.sep.join([usual_outdir, "lib"])
40 
41 # Move the current working directory to the ns-3-dev folder
42 os.chdir(ns3_path)
43 
44 # Cmake commands
45 num_threads = max(1, os.cpu_count() - 1)
46 cmake_build_project_command = "cmake --build {cmake_cache} -j".format(
47  ns3_path=ns3_path, cmake_cache=os.path.abspath(os.path.join(ns3_path, "cmake-cache"))
48 )
49 cmake_build_target_command = partial(
50  "cmake --build {cmake_cache} -j {jobs} --target {target}".format,
51  jobs=num_threads,
52  cmake_cache=os.path.abspath(os.path.join(ns3_path, "cmake-cache")),
53 )
54 win32 = sys.platform == "win32"
55 platform_makefiles = "MinGW Makefiles" if win32 else "Unix Makefiles"
56 ext = ".exe" if win32 else ""
57 
58 
59 def run_ns3(args, env=None, generator=platform_makefiles):
60  """!
61  Runs the ns3 wrapper script with arguments
62  @param args: string containing arguments that will get split before calling ns3
63  @param env: environment variables dictionary
64  @param generator: CMake generator
65  @return tuple containing (error code, stdout and stderr)
66  """
67  if "clean" in args:
68  possible_leftovers = ["contrib/borked", "contrib/calibre"]
69  for leftover in possible_leftovers:
70  if os.path.exists(leftover):
71  shutil.rmtree(leftover, ignore_errors=True)
72  if " -G " in args:
73  args = args.format(generator=generator)
74  if env is None:
75  env = {}
76  # Disable colored output by default during tests
77  env["CLICOLOR"] = "0"
78  return run_program(ns3_script, args, python=True, env=env)
79 
80 
81 # Adapted from https://github.com/metabrainz/picard/blob/master/picard/util/__init__.py
82 def run_program(program, args, python=False, cwd=ns3_path, env=None):
83  """!
84  Runs a program with the given arguments and returns a tuple containing (error code, stdout and stderr)
85  @param program: program to execute (or python script)
86  @param args: string containing arguments that will get split before calling the program
87  @param python: flag indicating whether the program is a python script
88  @param cwd: the working directory used that will be the root folder for the execution
89  @param env: environment variables dictionary
90  @return tuple containing (error code, stdout and stderr)
91  """
92  if type(args) != str:
93  raise Exception("args should be a string")
94 
95  # Include python interpreter if running a python script
96  if python:
97  arguments = [sys.executable, program]
98  else:
99  arguments = [program]
100 
101  if args != "":
102  arguments.extend(re.findall('(?:".*?"|\S)+', args)) # noqa
103 
104  for i in range(len(arguments)):
105  arguments[i] = arguments[i].replace('"', "")
106 
107  # Forward environment variables used by the ns3 script
108  current_env = os.environ.copy()
109 
110  # Add different environment variables
111  if env:
112  current_env.update(env)
113 
114  # Call program with arguments
115  ret = subprocess.run(
116  arguments,
117  stdin=subprocess.DEVNULL,
118  stdout=subprocess.PIPE,
119  stderr=subprocess.PIPE,
120  cwd=cwd, # run process from the ns-3-dev path
121  env=current_env,
122  )
123  # Return (error code, stdout and stderr)
124  return (
125  ret.returncode,
126  ret.stdout.decode(sys.stdout.encoding),
127  ret.stderr.decode(sys.stderr.encoding),
128  )
129 
130 
132  """!
133  Extracts the programs list from .lock-ns3
134  @return list of programs.
135  """
136  values = {}
137  with open(ns3_lock_filename, encoding="utf-8") as f:
138  exec(f.read(), globals(), values)
139 
140  programs_list = values["ns3_runnable_programs"]
141 
142  # Add .exe suffix to programs if on Windows
143  if win32:
144  programs_list = list(map(lambda x: x + ext, programs_list))
145  return programs_list
146 
147 
148 def get_libraries_list(lib_outdir=usual_lib_outdir):
149  """!
150  Gets a list of built libraries
151  @param lib_outdir: path containing libraries
152  @return list of built libraries.
153  """
154  libraries = glob.glob(lib_outdir + "/*", recursive=True)
155  return list(filter(lambda x: "scratch-nested-subdir-lib" not in x, libraries))
156 
157 
158 def get_headers_list(outdir=usual_outdir):
159  """!
160  Gets a list of header files
161  @param outdir: path containing headers
162  @return list of headers.
163  """
164  return glob.glob(outdir + "/**/*.h", recursive=True)
165 
166 
167 def read_lock_entry(entry):
168  """!
169  Read interesting entries from the .lock-ns3 file
170  @param entry: entry to read from .lock-ns3
171  @return value of the requested entry.
172  """
173  values = {}
174  with open(ns3_lock_filename, encoding="utf-8") as f:
175  exec(f.read(), globals(), values)
176  return values.get(entry, None)
177 
178 
180  """!
181  Check if tests are enabled in the .lock-ns3
182  @return bool.
183  """
184  return read_lock_entry("ENABLE_TESTS")
185 
186 
188  """
189  Check if tests are enabled in the .lock-ns3
190  @return list of enabled modules (prefixed with 'ns3-').
191  """
192  return read_lock_entry("NS3_ENABLED_MODULES")
193 
194 
196  """!
197  Python-on-whales wrapper for Docker-based ns-3 tests
198  """
199 
200  def __init__(self, currentTestCase: unittest.TestCase, containerName: str = "ubuntu:latest"):
201  """!
202  Create and start container with containerName in the current ns-3 directory
203  @param self: the current DockerContainerManager instance
204  @param currentTestCase: the test case instance creating the DockerContainerManager
205  @param containerName: name of the container image to be used
206  """
207  global DockerException
208  try:
209  from python_on_whales import docker
210  from python_on_whales.exceptions import DockerException
211  except ModuleNotFoundError:
212  docker = None # noqa
213  DockerException = None # noqa
214  currentTestCase.skipTest("python-on-whales was not found")
215 
216  # Import rootless docker settings from .bashrc
217  with open(os.path.expanduser("~/.bashrc"), "r", encoding="utf-8") as f:
218  docker_settings = re.findall("(DOCKER_.*=.*)", f.read())
219  for setting in docker_settings:
220  key, value = setting.split("=")
221  os.environ[key] = value
222  del docker_settings, setting, key, value
223 
224  # Create Docker client instance and start it
225 
226  self.containercontainer = docker.run(
227  containerName,
228  interactive=True,
229  detach=True,
230  tty=False,
231  volumes=[(ns3_path, "/ns-3-dev")],
232  )
233 
234  # Redefine the execute command of the container
235  def split_exec(docker_container, cmd):
236  return docker_container._execute(cmd.split(), workdir="/ns-3-dev")
237 
238  self.containercontainer._execute = self.containercontainer.execute
239  self.containercontainer.execute = partial(split_exec, self.containercontainer)
240 
241  def __enter__(self):
242  """!
243  Return the managed container when entiring the block "with DockerContainerManager() as container"
244  @param self: the current DockerContainerManager instance
245  @return container managed by DockerContainerManager.
246  """
247  return self.containercontainer
248 
249  def __exit__(self, exc_type, exc_val, exc_tb):
250  """!
251  Clean up the managed container at the end of the block "with DockerContainerManager() as container"
252  @param self: the current DockerContainerManager instance
253  @param exc_type: unused parameter
254  @param exc_val: unused parameter
255  @param exc_tb: unused parameter
256  @return None
257  """
258  self.containercontainer.stop()
259  self.containercontainer.remove()
260 
261 
262 class NS3UnusedSourcesTestCase(unittest.TestCase):
263  """!
264  ns-3 tests related to checking if source files were left behind, not being used by CMake
265  """
266 
267 
268  directory_and_files = {}
269 
270  def setUp(self):
271  """!
272  Scan all C++ source files and add them to a list based on their path
273  @return None
274  """
275  for root, dirs, files in os.walk(ns3_path):
276  if "gitlab-ci-local" in root:
277  continue
278  for name in files:
279  if name.endswith(".cc"):
280  path = os.path.join(root, name)
281  directory = os.path.dirname(path)
282  if directory not in self.directory_and_filesdirectory_and_files:
283  self.directory_and_filesdirectory_and_files[directory] = []
284  self.directory_and_filesdirectory_and_files[directory].append(path)
285 
287  """!
288  Test if all example source files are being used in their respective CMakeLists.txt
289  @return None
290  """
291  unused_sources = set()
292  for example_directory in self.directory_and_filesdirectory_and_files.keys():
293  # Skip non-example directories
294  if os.sep + "examples" not in example_directory:
295  continue
296 
297  # Open the examples CMakeLists.txt and read it
298  with open(
299  os.path.join(example_directory, "CMakeLists.txt"), "r", encoding="utf-8"
300  ) as f:
301  cmake_contents = f.read()
302 
303  # For each file, check if it is in the CMake contents
304  for file in self.directory_and_filesdirectory_and_files[example_directory]:
305  # We remove the .cc because some examples sources can be written as ${example_name}.cc
306  if os.path.basename(file).replace(".cc", "") not in cmake_contents:
307  unused_sources.add(file)
308 
309  self.assertListEqual([], list(unused_sources))
310 
312  """!
313  Test if all module source files are being used in their respective CMakeLists.txt
314  @return None
315  """
316  unused_sources = set()
317  for directory in self.directory_and_filesdirectory_and_files.keys():
318  # Skip examples and bindings directories
319  is_not_module = not ("src" in directory or "contrib" in directory)
320  is_example = os.sep + "examples" in directory
321  is_bindings = os.sep + "bindings" in directory
322 
323  if is_not_module or is_bindings or is_example:
324  continue
325 
326  # We can be in one of the module subdirectories (helper, model, test, bindings, etc)
327  # Navigate upwards until we hit a CMakeLists.txt
328  cmake_path = os.path.join(directory, "CMakeLists.txt")
329  while not os.path.exists(cmake_path):
330  parent_directory = os.path.dirname(os.path.dirname(cmake_path))
331  cmake_path = os.path.join(parent_directory, os.path.basename(cmake_path))
332 
333  # Open the module CMakeLists.txt and read it
334  with open(cmake_path, "r", encoding="utf-8") as f:
335  cmake_contents = f.read()
336 
337  # For each file, check if it is in the CMake contents
338  for file in self.directory_and_filesdirectory_and_files[directory]:
339  if os.path.basename(file) not in cmake_contents:
340  unused_sources.add(file)
341 
342  # Remove temporary exceptions
343  exceptions = [
344  "win32-system-wall-clock-ms.cc", # Should be removed with MR784
345  ]
346  for exception in exceptions:
347  for unused_source in unused_sources:
348  if os.path.basename(unused_source) == exception:
349  unused_sources.remove(unused_source)
350  break
351 
352  self.assertListEqual([], list(unused_sources))
353 
355  """!
356  Test if all utils source files are being used in their respective CMakeLists.txt
357  @return None
358  """
359  unused_sources = set()
360  for directory in self.directory_and_filesdirectory_and_files.keys():
361  # Skip directories that are not utils
362  is_module = "src" in directory or "contrib" in directory
363  if os.sep + "utils" not in directory or is_module:
364  continue
365 
366  # We can be in one of the module subdirectories (helper, model, test, bindings, etc)
367  # Navigate upwards until we hit a CMakeLists.txt
368  cmake_path = os.path.join(directory, "CMakeLists.txt")
369  while not os.path.exists(cmake_path):
370  parent_directory = os.path.dirname(os.path.dirname(cmake_path))
371  cmake_path = os.path.join(parent_directory, os.path.basename(cmake_path))
372 
373  # Open the module CMakeLists.txt and read it
374  with open(cmake_path, "r", encoding="utf-8") as f:
375  cmake_contents = f.read()
376 
377  # For each file, check if it is in the CMake contents
378  for file in self.directory_and_filesdirectory_and_files[directory]:
379  if os.path.basename(file) not in cmake_contents:
380  unused_sources.add(file)
381 
382  self.assertListEqual([], list(unused_sources))
383 
384 
385 class NS3DependenciesTestCase(unittest.TestCase):
386  """!
387  ns-3 tests related to dependencies
388  """
389 
391  """!
392  Checks if headers from different modules (src/A, contrib/B) that are included by
393  the current module (src/C) source files correspond to the list of linked modules
394  LIBNAME C
395  LIBRARIES_TO_LINK A (missing B)
396  @return None
397  """
398  modules = {}
399  headers_to_modules = {}
400  module_paths = glob.glob(ns3_path + "/src/*/") + glob.glob(ns3_path + "/contrib/*/")
401 
402  for path in module_paths:
403  # Open the module CMakeLists.txt and read it
404  cmake_path = os.path.join(path, "CMakeLists.txt")
405  with open(cmake_path, "r", encoding="utf-8") as f:
406  cmake_contents = f.readlines()
407 
408  module_name = os.path.relpath(path, ns3_path)
409  module_name_nodir = module_name.replace("src/", "").replace("contrib/", "")
410  modules[module_name_nodir] = {
411  "sources": set(),
412  "headers": set(),
413  "libraries": set(),
414  "included_headers": set(),
415  "included_libraries": set(),
416  }
417 
418  # Separate list of source files and header files
419  for line in cmake_contents:
420  source_file_path = re.findall(r"\b(?:[^\s]+\.[ch]{1,2})\b", line.strip())
421  if not source_file_path:
422  continue
423  source_file_path = source_file_path[0]
424  base_name = os.path.basename(source_file_path)
425  if not os.path.exists(os.path.join(path, source_file_path)):
426  continue
427 
428  if ".h" in source_file_path:
429  # Register all module headers as module headers and sources
430  modules[module_name_nodir]["headers"].add(base_name)
431  modules[module_name_nodir]["sources"].add(base_name)
432 
433  # Register the header as part of the current module
434  headers_to_modules[base_name] = module_name_nodir
435 
436  if ".cc" in source_file_path:
437  # Register the source file as part of the current module
438  modules[module_name_nodir]["sources"].add(base_name)
439 
440  if ".cc" in source_file_path or ".h" in source_file_path:
441  # Extract includes from headers and source files and then add to a list of included headers
442  source_file = os.path.join(ns3_path, module_name, source_file_path)
443  with open(source_file, "r", encoding="utf-8") as f:
444  source_contents = f.read()
445  modules[module_name_nodir]["included_headers"].update(
446  map(
447  lambda x: x.replace("ns3/", ""),
448  re.findall('#include.*["|<](.*)["|>]', source_contents),
449  )
450  )
451  continue
452 
453  # Extract libraries linked to the module
454  modules[module_name_nodir]["libraries"].update(
455  re.findall("\${lib(.*?)}", "".join(cmake_contents))
456  )
457  modules[module_name_nodir]["libraries"] = list(
458  filter(
459  lambda x: x
460  not in ["raries_to_link", module_name_nodir, module_name_nodir + "-obj"],
461  modules[module_name_nodir]["libraries"],
462  )
463  )
464 
465  # Now that we have all the information we need, check if we have all the included libraries linked
466  all_project_headers = set(headers_to_modules.keys())
467 
468  sys.stderr.flush()
469  print(file=sys.stderr)
470  for module in sorted(modules):
471  external_headers = modules[module]["included_headers"].difference(all_project_headers)
472  project_headers_included = modules[module]["included_headers"].difference(
473  external_headers
474  )
475  modules[module]["included_libraries"] = set(
476  [headers_to_modules[x] for x in project_headers_included]
477  ).difference({module})
478 
479  diff = modules[module]["included_libraries"].difference(modules[module]["libraries"])
480 
481  # Find graph with least amount of edges based on included_libraries
482  def recursive_check_dependencies(checked_module):
483  # Remove direct explicit dependencies
484  for module_to_link in modules[checked_module]["included_libraries"]:
485  modules[checked_module]["included_libraries"] = set(
486  modules[checked_module]["included_libraries"]
487  ) - set(modules[module_to_link]["included_libraries"])
488 
489  for module_to_link in modules[checked_module]["included_libraries"]:
490  recursive_check_dependencies(module_to_link)
491 
492  # Remove unnecessary implicit dependencies
493  def is_implicitly_linked(searched_module, current_module):
494  if len(modules[current_module]["included_libraries"]) == 0:
495  return False
496  if searched_module in modules[current_module]["included_libraries"]:
497  return True
498  for module in modules[current_module]["included_libraries"]:
499  if is_implicitly_linked(searched_module, module):
500  return True
501  return False
502 
503  from itertools import combinations
504 
505  implicitly_linked = set()
506  for dep1, dep2 in combinations(modules[checked_module]["included_libraries"], 2):
507  if is_implicitly_linked(dep1, dep2):
508  implicitly_linked.add(dep1)
509  if is_implicitly_linked(dep2, dep1):
510  implicitly_linked.add(dep2)
511 
512  modules[checked_module]["included_libraries"] = (
513  set(modules[checked_module]["included_libraries"]) - implicitly_linked
514  )
515 
516  for module in modules:
517  recursive_check_dependencies(module)
518 
519  # Print findings
520  for module in sorted(modules):
521  if module == "test":
522  continue
523  minimal_linking_set = ", ".join(modules[module]["included_libraries"])
524  unnecessarily_linked = ", ".join(
525  set(modules[module]["libraries"]) - set(modules[module]["included_libraries"])
526  )
527  missing_linked = ", ".join(
528  set(modules[module]["included_libraries"]) - set(modules[module]["libraries"])
529  )
530  if unnecessarily_linked:
531  print(f"Module '{module}' unnecessarily linked: {unnecessarily_linked}.")
532  if missing_linked:
533  print(f"Module '{module}' missing linked: {missing_linked}.")
534  if unnecessarily_linked or missing_linked:
535  print(f"Module '{module}' minimal linking set: {minimal_linking_set}.")
536  self.assertTrue(True)
537 
538 
539 class NS3StyleTestCase(unittest.TestCase):
540  """!
541  ns-3 tests to check if the source code, whitespaces and CMake formatting
542  are according to the coding style
543  """
544 
545 
546  starting_diff = None
547 
548  repo = None
549 
550  def setUp(self) -> None:
551  """!
552  Import GitRepo and load the original diff state of the repository before the tests
553  @return None
554  """
555  if not NS3StyleTestCase.starting_diff:
556  if shutil.which("git") is None:
557  self.skipTest("Git is not available")
558 
559  try:
560  import git.exc # noqa
561  from git import Repo # noqa
562  except ImportError:
563  self.skipTest("GitPython is not available")
564 
565  try:
566  repo = Repo(ns3_path) # noqa
567  except git.exc.InvalidGitRepositoryError: # noqa
568  self.skipTest("ns-3 directory does not contain a .git directory")
569 
570  hcommit = repo.head.commit # noqa
571  NS3StyleTestCase.starting_diff = hcommit.diff(None)
572  NS3StyleTestCase.repo = repo
573 
574  if NS3StyleTestCase.starting_diff is None:
575  self.skipTest("Unmet dependencies")
576 
578  """!
579  Check if there is any difference between tracked file after
580  applying cmake-format
581  @return None
582  """
583 
584  for required_program in ["cmake", "cmake-format"]:
585  if shutil.which(required_program) is None:
586  self.skipTest("%s was not found" % required_program)
587 
588  # Configure ns-3 to get the cmake-format target
589  return_code, stdout, stderr = run_ns3("configure")
590  self.assertEqual(return_code, 0)
591 
592  # Build/run cmake-format
593  return_code, stdout, stderr = run_ns3("build cmake-format")
594  self.assertEqual(return_code, 0)
595 
596  # Clean the ns-3 configuration
597  return_code, stdout, stderr = run_ns3("clean")
598  self.assertEqual(return_code, 0)
599 
600  # Check if the diff still is the same
601  new_diff = NS3StyleTestCase.repo.head.commit.diff(None)
602  self.assertEqual(NS3StyleTestCase.starting_diff, new_diff)
603 
604 
605 class NS3CommonSettingsTestCase(unittest.TestCase):
606  """!
607  ns3 tests related to generic options
608  """
609 
610  def setUp(self):
611  """!
612  Clean configuration/build artifacts before common commands
613  @return None
614  """
615  super().setUp()
616  # No special setup for common test cases other than making sure we are working on a clean directory.
617  run_ns3("clean")
618 
619  def test_01_NoOption(self):
620  """!
621  Test not passing any arguments to
622  @return None
623  """
624  return_code, stdout, stderr = run_ns3("")
625  self.assertEqual(return_code, 1)
626  self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
627 
629  """!
630  Test only passing --quiet argument to ns3
631  @return None
632  """
633  return_code, stdout, stderr = run_ns3("--quiet")
634  self.assertEqual(return_code, 1)
635  self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
636 
638  """!
639  Test only passing 'show config' argument to ns3
640  @return None
641  """
642  return_code, stdout, stderr = run_ns3("show config")
643  self.assertEqual(return_code, 1)
644  self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
645 
647  """!
648  Test only passing 'show profile' argument to ns3
649  @return None
650  """
651  return_code, stdout, stderr = run_ns3("show profile")
652  self.assertEqual(return_code, 1)
653  self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
654 
656  """!
657  Test only passing 'show version' argument to ns3
658  @return None
659  """
660  return_code, stdout, stderr = run_ns3("show version")
661  self.assertEqual(return_code, 1)
662  self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
663 
664 
665 class NS3ConfigureBuildProfileTestCase(unittest.TestCase):
666  """!
667  ns3 tests related to build profiles
668  """
669 
670  def setUp(self):
671  """!
672  Clean configuration/build artifacts before testing configuration settings
673  @return None
674  """
675  super().setUp()
676  # No special setup for common test cases other than making sure we are working on a clean directory.
677  run_ns3("clean")
678 
679  def test_01_Debug(self):
680  """!
681  Test the debug build
682  @return None
683  """
684  return_code, stdout, stderr = run_ns3(
685  'configure -G "{generator}" -d debug --enable-verbose'
686  )
687  self.assertEqual(return_code, 0)
688  self.assertIn("Build profile : debug", stdout)
689  self.assertIn("Build files have been written to", stdout)
690 
691  # Build core to check if profile suffixes match the expected.
692  return_code, stdout, stderr = run_ns3("build core")
693  self.assertEqual(return_code, 0)
694  self.assertIn("Built target libcore", stdout)
695 
696  libraries = get_libraries_list()
697  self.assertGreater(len(libraries), 0)
698  self.assertIn("core-debug", libraries[0])
699 
700  def test_02_Release(self):
701  """!
702  Test the release build
703  @return None
704  """
705  return_code, stdout, stderr = run_ns3('configure -G "{generator}" -d release')
706  self.assertEqual(return_code, 0)
707  self.assertIn("Build profile : release", stdout)
708  self.assertIn("Build files have been written to", stdout)
709 
710  def test_03_Optimized(self):
711  """!
712  Test the optimized build
713  @return None
714  """
715  return_code, stdout, stderr = run_ns3(
716  'configure -G "{generator}" -d optimized --enable-verbose'
717  )
718  self.assertEqual(return_code, 0)
719  self.assertIn("Build profile : optimized", stdout)
720  self.assertIn("Build files have been written to", stdout)
721 
722  # Build core to check if profile suffixes match the expected
723  return_code, stdout, stderr = run_ns3("build core")
724  self.assertEqual(return_code, 0)
725  self.assertIn("Built target libcore", stdout)
726 
727  libraries = get_libraries_list()
728  self.assertGreater(len(libraries), 0)
729  self.assertIn("core-optimized", libraries[0])
730 
731  def test_04_Typo(self):
732  """!
733  Test a build type with a typo
734  @return None
735  """
736  return_code, stdout, stderr = run_ns3('configure -G "{generator}" -d Optimized')
737  self.assertEqual(return_code, 2)
738  self.assertIn("invalid choice: 'Optimized'", stderr)
739 
740  def test_05_TYPO(self):
741  """!
742  Test a build type with another typo
743  @return None
744  """
745  return_code, stdout, stderr = run_ns3('configure -G "{generator}" -d OPTIMIZED')
746  self.assertEqual(return_code, 2)
747  self.assertIn("invalid choice: 'OPTIMIZED'", stderr)
748 
750  """!
751  Replace settings set by default (e.g. ASSERT/LOGs enabled in debug builds and disabled in default ones)
752  @return None
753  """
754  return_code, _, _ = run_ns3("clean")
755  self.assertEqual(return_code, 0)
756 
757  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --dry-run -d debug')
758  self.assertEqual(return_code, 0)
759  self.assertIn(
760  "-DCMAKE_BUILD_TYPE=debug -DNS3_ASSERT=ON -DNS3_LOG=ON -DNS3_WARNINGS_AS_ERRORS=ON -DNS3_NATIVE_OPTIMIZATIONS=OFF",
761  stdout,
762  )
763 
764  return_code, stdout, stderr = run_ns3(
765  'configure -G "{generator}" --dry-run -d debug --disable-asserts --disable-logs --disable-werror'
766  )
767  self.assertEqual(return_code, 0)
768  self.assertIn(
769  "-DCMAKE_BUILD_TYPE=debug -DNS3_NATIVE_OPTIMIZATIONS=OFF -DNS3_ASSERT=OFF -DNS3_LOG=OFF -DNS3_WARNINGS_AS_ERRORS=OFF",
770  stdout,
771  )
772 
773  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --dry-run')
774  self.assertEqual(return_code, 0)
775  self.assertIn(
776  "-DCMAKE_BUILD_TYPE=default -DNS3_ASSERT=ON -DNS3_LOG=ON -DNS3_WARNINGS_AS_ERRORS=OFF -DNS3_NATIVE_OPTIMIZATIONS=OFF",
777  stdout,
778  )
779 
780  return_code, stdout, stderr = run_ns3(
781  'configure -G "{generator}" --dry-run --enable-asserts --enable-logs --enable-werror'
782  )
783  self.assertEqual(return_code, 0)
784  self.assertIn(
785  "-DCMAKE_BUILD_TYPE=default -DNS3_ASSERT=ON -DNS3_LOG=ON -DNS3_NATIVE_OPTIMIZATIONS=OFF -DNS3_ASSERT=ON -DNS3_LOG=ON -DNS3_WARNINGS_AS_ERRORS=ON",
786  stdout,
787  )
788 
789 
790 class NS3BaseTestCase(unittest.TestCase):
791  """!
792  Generic test case with basic function inherited by more complex tests.
793  """
794 
795  def config_ok(self, return_code, stdout, stderr):
796  """!
797  Check if configuration for release mode worked normally
798  @param return_code: return code from CMake
799  @param stdout: output from CMake.
800  @param stderr: error from CMake.
801  @return None
802  """
803  self.assertEqual(return_code, 0)
804  self.assertIn("Build profile : release", stdout)
805  self.assertIn("Build files have been written to", stdout)
806  self.assertNotIn("uninitialized variable", stderr)
807 
808  def setUp(self):
809  """!
810  Clean configuration/build artifacts before testing configuration and build settings
811  After configuring the build as release,
812  check if configuration worked and check expected output files.
813  @return None
814  """
815  super().setUp()
816 
817  if os.path.exists(ns3rc_script):
818  os.remove(ns3rc_script)
819 
820  # Reconfigure from scratch before each test
821  run_ns3("clean")
822  return_code, stdout, stderr = run_ns3(
823  'configure -G "{generator}" -d release --enable-verbose'
824  )
825  self.config_okconfig_ok(return_code, stdout, stderr)
826 
827  # Check if .lock-ns3 exists, then read to get list of executables.
828  self.assertTrue(os.path.exists(ns3_lock_filename))
829 
830  self.ns3_executablesns3_executables = get_programs_list()
831 
832  # Check if .lock-ns3 exists than read to get the list of enabled modules.
833  self.assertTrue(os.path.exists(ns3_lock_filename))
834 
835  self.ns3_modulesns3_modules = get_enabled_modules()
836 
837 
839  """!
840  Test ns3 configuration options
841  """
842 
843  def setUp(self):
844  """!
845  Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
846  @return None
847  """
848  super().setUp()
849 
850  def test_01_Examples(self):
851  """!
852  Test enabling and disabling examples
853  @return None
854  """
855  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --enable-examples')
856 
857  # This just tests if we didn't break anything, not that we actually have enabled anything.
858  self.config_okconfig_ok(return_code, stdout, stderr)
859 
860  # If nothing went wrong, we should have more executables in the list after enabling the examples.
861 
862  self.assertGreater(len(get_programs_list()), len(self.ns3_executablesns3_executables))
863 
864  # Now we disabled them back.
865  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --disable-examples')
866 
867  # This just tests if we didn't break anything, not that we actually have enabled anything.
868  self.config_okconfig_ok(return_code, stdout, stderr)
869 
870  # Then check if they went back to the original list.
871  self.assertEqual(len(get_programs_list()), len(self.ns3_executablesns3_executables))
872 
873  def test_02_Tests(self):
874  """!
875  Test enabling and disabling tests
876  @return None
877  """
878  # Try enabling tests
879  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --enable-tests')
880  self.config_okconfig_ok(return_code, stdout, stderr)
881 
882  # Then try building the libcore test
883  return_code, stdout, stderr = run_ns3("build core-test")
884 
885  # If nothing went wrong, this should have worked
886  self.assertEqual(return_code, 0)
887  self.assertIn("Built target libcore-test", stdout)
888 
889  # Now we disabled the tests
890  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --disable-tests')
891  self.config_okconfig_ok(return_code, stdout, stderr)
892 
893  # Now building the library test should fail
894  return_code, stdout, stderr = run_ns3("build core-test")
895 
896  # Then check if they went back to the original list
897  self.assertEqual(return_code, 1)
898  self.assertIn("Target to build does not exist: core-test", stdout)
899 
901  """!
902  Test enabling specific modules
903  @return None
904  """
905  # Try filtering enabled modules to network+Wi-Fi and their dependencies
906  return_code, stdout, stderr = run_ns3(
907  "configure -G \"{generator}\" --enable-modules='network;wifi'"
908  )
909  self.config_okconfig_ok(return_code, stdout, stderr)
910 
911  # At this point we should have fewer modules
912  enabled_modules = get_enabled_modules()
913 
914  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
915  self.assertIn("ns3-network", enabled_modules)
916  self.assertIn("ns3-wifi", enabled_modules)
917 
918  # Try enabling only core
919  return_code, stdout, stderr = run_ns3(
920  "configure -G \"{generator}\" --enable-modules='core'"
921  )
922  self.config_okconfig_ok(return_code, stdout, stderr)
923  self.assertIn("ns3-core", get_enabled_modules())
924 
925  # Try cleaning the list of enabled modules to reset to the normal configuration.
926  return_code, stdout, stderr = run_ns3("configure -G \"{generator}\" --enable-modules=''")
927  self.config_okconfig_ok(return_code, stdout, stderr)
928 
929  # At this point we should have the same amount of modules that we had when we started.
930  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
931 
933  """!
934  Test disabling specific modules
935  @return None
936  """
937  # Try filtering disabled modules to disable lte and modules that depend on it.
938  return_code, stdout, stderr = run_ns3(
939  "configure -G \"{generator}\" --disable-modules='lte;wimax'"
940  )
941  self.config_okconfig_ok(return_code, stdout, stderr)
942 
943  # At this point we should have fewer modules.
944  enabled_modules = get_enabled_modules()
945  self.assertLess(len(enabled_modules), len(self.ns3_modulesns3_modules))
946  self.assertNotIn("ns3-lte", enabled_modules)
947  self.assertNotIn("ns3-wimax", enabled_modules)
948 
949  # Try cleaning the list of enabled modules to reset to the normal configuration.
950  return_code, stdout, stderr = run_ns3("configure -G \"{generator}\" --disable-modules=''")
951  self.config_okconfig_ok(return_code, stdout, stderr)
952 
953  # At this point we should have the same amount of modules that we had when we started.
954  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
955 
957  """!
958  Test enabling comma-separated (waf-style) examples
959  @return None
960  """
961  # Try filtering enabled modules to network+Wi-Fi and their dependencies.
962  return_code, stdout, stderr = run_ns3(
963  "configure -G \"{generator}\" --enable-modules='network,wifi'"
964  )
965  self.config_okconfig_ok(return_code, stdout, stderr)
966 
967  # At this point we should have fewer modules.
968  enabled_modules = get_enabled_modules()
969  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
970  self.assertIn("ns3-network", enabled_modules)
971  self.assertIn("ns3-wifi", enabled_modules)
972 
973  # Try cleaning the list of enabled modules to reset to the normal configuration.
974  return_code, stdout, stderr = run_ns3("configure -G \"{generator}\" --enable-modules=''")
975  self.config_okconfig_ok(return_code, stdout, stderr)
976 
977  # At this point we should have the same amount of modules that we had when we started.
978  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
979 
981  """!
982  Test disabling comma-separated (waf-style) examples
983  @return None
984  """
985  # Try filtering disabled modules to disable lte and modules that depend on it.
986  return_code, stdout, stderr = run_ns3(
987  "configure -G \"{generator}\" --disable-modules='lte,mpi'"
988  )
989  self.config_okconfig_ok(return_code, stdout, stderr)
990 
991  # At this point we should have fewer modules.
992  enabled_modules = get_enabled_modules()
993  self.assertLess(len(enabled_modules), len(self.ns3_modulesns3_modules))
994  self.assertNotIn("ns3-lte", enabled_modules)
995  self.assertNotIn("ns3-mpi", enabled_modules)
996 
997  # Try cleaning the list of enabled modules to reset to the normal configuration.
998  return_code, stdout, stderr = run_ns3("configure -G \"{generator}\" --disable-modules=''")
999  self.config_okconfig_ok(return_code, stdout, stderr)
1000 
1001  # At this point we should have the same amount of modules that we had when we started.
1002  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
1003 
1004  def test_07_Ns3rc(self):
1005  """!
1006  Test loading settings from the ns3rc config file
1007  @return None
1008  """
1009 
1010  class ns3rc_str: # noqa
1011 
1012  ns3rc_python_template = "# ! /usr/bin/env python\
1013  \
1014  # A list of the modules that will be enabled when ns-3 is run.\
1015  # Modules that depend on the listed modules will be enabled also.\
1016  #\
1017  # All modules can be enabled by choosing 'all_modules'.\
1018  modules_enabled = [{modules}]\
1019  \
1020  # Set this equal to true if you want examples to be run.\
1021  examples_enabled = {examples}\
1022  \
1023  # Set this equal to true if you want tests to be run.\
1024  tests_enabled = {tests}\
1025  "
1026 
1027 
1028  ns3rc_cmake_template = "set(ns3rc_tests_enabled {tests})\
1029  \nset(ns3rc_examples_enabled {examples})\
1030  \nset(ns3rc_enabled_modules {modules})\
1031  "
1032 
1033 
1034  ns3rc_templates = {"python": ns3rc_python_template, "cmake": ns3rc_cmake_template}
1035 
1036  def __init__(self, type_ns3rc):
1037 
1038  self.typetype = type_ns3rc
1039 
1040  def format(self, **args):
1041  # Convert arguments from python-based ns3rc format to CMake
1042  if self.typetype == "cmake":
1043  args["modules"] = (
1044  args["modules"].replace("'", "").replace('"', "").replace(",", " ")
1045  )
1046  args["examples"] = "ON" if args["examples"] == "True" else "OFF"
1047  args["tests"] = "ON" if args["tests"] == "True" else "OFF"
1048 
1049  formatted_string = ns3rc_str.ns3rc_templates[self.typetype].format(**args)
1050 
1051  # Return formatted string
1052  return formatted_string
1053 
1054  @staticmethod
1055  def types():
1056  return ns3rc_str.ns3rc_templates.keys()
1057 
1058  for ns3rc_type in ns3rc_str.types():
1059  # Replace default format method from string with a custom one
1060  ns3rc_template = ns3rc_str(ns3rc_type)
1061 
1062  # Now we repeat the command line tests but with the ns3rc file.
1063  with open(ns3rc_script, "w", encoding="utf-8") as f:
1064  f.write(ns3rc_template.format(modules="'lte'", examples="False", tests="True"))
1065 
1066  # Reconfigure.
1067  return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1068  self.config_okconfig_ok(return_code, stdout, stderr)
1069 
1070  # Check.
1071  enabled_modules = get_enabled_modules()
1072  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
1073  self.assertIn("ns3-lte", enabled_modules)
1074  self.assertTrue(get_test_enabled())
1075  self.assertLessEqual(len(get_programs_list()), len(self.ns3_executablesns3_executables))
1076 
1077  # Replace the ns3rc file with the wifi module, enabling examples and disabling tests
1078  with open(ns3rc_script, "w", encoding="utf-8") as f:
1079  f.write(ns3rc_template.format(modules="'wifi'", examples="True", tests="False"))
1080 
1081  # Reconfigure
1082  return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1083  self.config_okconfig_ok(return_code, stdout, stderr)
1084 
1085  # Check
1086  enabled_modules = get_enabled_modules()
1087  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
1088  self.assertIn("ns3-wifi", enabled_modules)
1089  self.assertFalse(get_test_enabled())
1090  self.assertGreater(len(get_programs_list()), len(self.ns3_executablesns3_executables))
1091 
1092  # Replace the ns3rc file with multiple modules
1093  with open(ns3rc_script, "w", encoding="utf-8") as f:
1094  f.write(
1095  ns3rc_template.format(
1096  modules="'core','network'", examples="True", tests="False"
1097  )
1098  )
1099 
1100  # Reconfigure
1101  return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1102  self.config_okconfig_ok(return_code, stdout, stderr)
1103 
1104  # Check
1105  enabled_modules = get_enabled_modules()
1106  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
1107  self.assertIn("ns3-core", enabled_modules)
1108  self.assertIn("ns3-network", enabled_modules)
1109  self.assertFalse(get_test_enabled())
1110  self.assertGreater(len(get_programs_list()), len(self.ns3_executablesns3_executables))
1111 
1112  # Replace the ns3rc file with multiple modules,
1113  # in various different ways and with comments
1114  with open(ns3rc_script, "w", encoding="utf-8") as f:
1115  if ns3rc_type == "python":
1116  f.write(
1117  ns3rc_template.format(
1118  modules="""'core', #comment
1119  'lte',
1120  #comment2,
1121  #comment3
1122  'network', 'internet','wimax'""",
1123  examples="True",
1124  tests="True",
1125  )
1126  )
1127  else:
1128  f.write(
1129  ns3rc_template.format(
1130  modules="'core', 'lte', 'network', 'internet', 'wimax'",
1131  examples="True",
1132  tests="True",
1133  )
1134  )
1135  # Reconfigure
1136  return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1137  self.config_okconfig_ok(return_code, stdout, stderr)
1138 
1139  # Check
1140  enabled_modules = get_enabled_modules()
1141  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
1142  self.assertIn("ns3-core", enabled_modules)
1143  self.assertIn("ns3-internet", enabled_modules)
1144  self.assertIn("ns3-lte", enabled_modules)
1145  self.assertIn("ns3-wimax", enabled_modules)
1146  self.assertTrue(get_test_enabled())
1147  self.assertGreater(len(get_programs_list()), len(self.ns3_executablesns3_executables))
1148 
1149  # Then we roll back by removing the ns3rc config file
1150  os.remove(ns3rc_script)
1151 
1152  # Reconfigure
1153  return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1154  self.config_okconfig_ok(return_code, stdout, stderr)
1155 
1156  # Check
1157  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
1158  self.assertFalse(get_test_enabled())
1159  self.assertEqual(len(get_programs_list()), len(self.ns3_executablesns3_executables))
1160 
1161  def test_08_DryRun(self):
1162  """!
1163  Test dry-run (printing commands to be executed instead of running them)
1164  @return None
1165  """
1166  run_ns3("clean")
1167 
1168  # Try dry-run before and after the positional commands (outputs should match)
1169  for positional_command in ["configure", "build", "clean"]:
1170  return_code, stdout, stderr = run_ns3("--dry-run %s" % positional_command)
1171  return_code1, stdout1, stderr1 = run_ns3("%s --dry-run" % positional_command)
1172 
1173  self.assertEqual(return_code, return_code1)
1174  self.assertEqual(stdout, stdout1)
1175  self.assertEqual(stderr, stderr1)
1176 
1177  run_ns3("clean")
1178 
1179  # Build target before using below
1180  run_ns3('configure -G "{generator}" -d release --enable-verbose')
1181  run_ns3("build scratch-simulator")
1182 
1183  # Run all cases and then check outputs
1184  return_code0, stdout0, stderr0 = run_ns3("--dry-run run scratch-simulator")
1185  return_code1, stdout1, stderr1 = run_ns3("run scratch-simulator")
1186  return_code2, stdout2, stderr2 = run_ns3("--dry-run run scratch-simulator --no-build")
1187  return_code3, stdout3, stderr3 = run_ns3("run scratch-simulator --no-build")
1188 
1189  # Return code and stderr should be the same for all of them.
1190  self.assertEqual(sum([return_code0, return_code1, return_code2, return_code3]), 0)
1191  self.assertEqual([stderr0, stderr1, stderr2, stderr3], [""] * 4)
1192 
1193  scratch_path = None
1194  for program in get_programs_list():
1195  if "scratch-simulator" in program and "subdir" not in program:
1196  scratch_path = program
1197  break
1198 
1199  # Scratches currently have a 'scratch_' prefix in their CMake targets
1200  # Case 0: dry-run + run (should print commands to build target and then run)
1201  self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout0)
1202  self.assertIn(scratch_path, stdout0)
1203 
1204  # Case 1: run (should print only make build message)
1205  self.assertNotIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout1)
1206  self.assertIn("Built target", stdout1)
1207  self.assertNotIn(scratch_path, stdout1)
1208 
1209  # Case 2: dry-run + run-no-build (should print commands to run the target)
1210  self.assertIn("The following commands would be executed:", stdout2)
1211  self.assertIn(scratch_path, stdout2)
1212 
1213  # Case 3: run-no-build (should print the target output only)
1214  self.assertNotIn("Finished executing the following commands:", stdout3)
1215  self.assertNotIn(scratch_path, stdout3)
1216 
1218  """!
1219  Test if ns3 is propagating back the return code from the executables called with the run command
1220  @return None
1221  """
1222  # From this point forward we are reconfiguring in debug mode
1223  return_code, _, _ = run_ns3("clean")
1224  self.assertEqual(return_code, 0)
1225 
1226  return_code, _, _ = run_ns3('configure -G "{generator}" --enable-examples --enable-tests')
1227  self.assertEqual(return_code, 0)
1228 
1229  # Build necessary executables
1230  return_code, stdout, stderr = run_ns3("build command-line-example test-runner")
1231  self.assertEqual(return_code, 0)
1232 
1233  # Now some tests will succeed normally
1234  return_code, stdout, stderr = run_ns3(
1235  'run "test-runner --test-name=command-line" --no-build'
1236  )
1237  self.assertEqual(return_code, 0)
1238 
1239  # Now some tests will fail during NS_COMMANDLINE_INTROSPECTION
1240  return_code, stdout, stderr = run_ns3(
1241  'run "test-runner --test-name=command-line" --no-build',
1242  env={"NS_COMMANDLINE_INTROSPECTION": ".."},
1243  )
1244  self.assertNotEqual(return_code, 0)
1245 
1246  # Cause a sigsegv
1247  sigsegv_example = os.path.join(ns3_path, "scratch", "sigsegv.cc")
1248  with open(sigsegv_example, "w", encoding="utf-8") as f:
1249  f.write(
1250  """
1251  int main (int argc, char *argv[])
1252  {
1253  char *s = "hello world"; *s = 'H';
1254  return 0;
1255  }
1256  """
1257  )
1258  return_code, stdout, stderr = run_ns3("run sigsegv")
1259  if win32:
1260  self.assertEqual(return_code, 4294967295) # unsigned -1
1261  self.assertIn("sigsegv-default.exe' returned non-zero exit status", stdout)
1262  else:
1263  self.assertEqual(return_code, 245)
1264  self.assertIn("sigsegv-default' died with <Signals.SIGSEGV: 11>", stdout)
1265 
1266  # Cause an abort
1267  abort_example = os.path.join(ns3_path, "scratch", "abort.cc")
1268  with open(abort_example, "w", encoding="utf-8") as f:
1269  f.write(
1270  """
1271  #include "ns3/core-module.h"
1272 
1273  using namespace ns3;
1274  int main (int argc, char *argv[])
1275  {
1276  NS_ABORT_IF(true);
1277  return 0;
1278  }
1279  """
1280  )
1281  return_code, stdout, stderr = run_ns3("run abort")
1282  if win32:
1283  self.assertEqual(return_code, 3)
1284  self.assertIn("abort-default.exe' returned non-zero exit status", stdout)
1285  else:
1286  self.assertEqual(return_code, 250)
1287  self.assertIn("abort-default' died with <Signals.SIGABRT: 6>", stdout)
1288 
1289  os.remove(sigsegv_example)
1290  os.remove(abort_example)
1291 
1293  """!
1294  Test passing 'show config' argument to ns3 to get the configuration table
1295  @return None
1296  """
1297  return_code, stdout, stderr = run_ns3("show config")
1298  self.assertEqual(return_code, 0)
1299  self.assertIn("Summary of ns-3 settings", stdout)
1300 
1302  """!
1303  Test passing 'show profile' argument to ns3 to get the build profile
1304  @return None
1305  """
1306  return_code, stdout, stderr = run_ns3("show profile")
1307  self.assertEqual(return_code, 0)
1308  self.assertIn("Build profile: release", stdout)
1309 
1311  """!
1312  Test passing 'show version' argument to ns3 to get the build version
1313  @return None
1314  """
1315  if shutil.which("git") is None:
1316  self.skipTest("git is not available")
1317 
1318  return_code, _, _ = run_ns3('configure -G "{generator}" --enable-build-version')
1319  self.assertEqual(return_code, 0)
1320 
1321  return_code, stdout, stderr = run_ns3("show version")
1322  self.assertEqual(return_code, 0)
1323  self.assertIn("ns-3 version:", stdout)
1324 
1326  """!
1327  Test if CMake target names for scratches and ns3 shortcuts
1328  are working correctly
1329  @return None
1330  """
1331 
1332  test_files = [
1333  "scratch/main.cc",
1334  "scratch/empty.cc",
1335  "scratch/subdir1/main.cc",
1336  "scratch/subdir2/main.cc",
1337  "scratch/main.test.dots.in.name.cc",
1338  ]
1339  backup_files = ["scratch/.main.cc"] # hidden files should be ignored
1340 
1341  # Create test scratch files
1342  for path in test_files + backup_files:
1343  filepath = os.path.join(ns3_path, path)
1344  os.makedirs(os.path.dirname(filepath), exist_ok=True)
1345  with open(filepath, "w", encoding="utf-8") as f:
1346  if "main" in path:
1347  f.write("int main (int argc, char *argv[]){}")
1348  else:
1349  # no main function will prevent this target from
1350  # being created, we should skip it and continue
1351  # processing without crashing
1352  f.write("")
1353 
1354  # Reload the cmake cache to pick them up
1355  return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1356  self.assertEqual(return_code, 0)
1357 
1358  # Try to build them with ns3 and cmake
1359  for path in test_files + backup_files:
1360  path = path.replace(".cc", "")
1361  return_code1, stdout1, stderr1 = run_program(
1362  "cmake",
1363  "--build . --target %s -j %d" % (path.replace("/", "_"), num_threads),
1364  cwd=os.path.join(ns3_path, "cmake-cache"),
1365  )
1366  return_code2, stdout2, stderr2 = run_ns3("build %s" % path)
1367  if "main" in path and ".main" not in path:
1368  self.assertEqual(return_code1, 0)
1369  self.assertEqual(return_code2, 0)
1370  else:
1371  self.assertEqual(return_code1, 2)
1372  self.assertEqual(return_code2, 1)
1373 
1374  # Try to run them
1375  for path in test_files:
1376  path = path.replace(".cc", "")
1377  return_code, stdout, stderr = run_ns3("run %s --no-build" % path)
1378  if "main" in path:
1379  self.assertEqual(return_code, 0)
1380  else:
1381  self.assertEqual(return_code, 1)
1382 
1383  run_ns3("clean")
1384  with DockerContainerManager(self, "ubuntu:20.04") as container:
1385  container.execute("apt-get update")
1386  container.execute("apt-get install -y python3 cmake g++ ninja-build")
1387  try:
1388  container.execute(
1389  "./ns3 configure --enable-modules=core,network,internet -- -DCMAKE_CXX_COMPILER=/usr/bin/g++"
1390  )
1391  except DockerException as e:
1392  self.fail()
1393  for path in test_files:
1394  path = path.replace(".cc", "")
1395  try:
1396  container.execute(f"./ns3 run {path}")
1397  except DockerException as e:
1398  if "main" in path:
1399  self.fail()
1400  run_ns3("clean")
1401 
1402  # Delete the test files and reconfigure to clean them up
1403  for path in test_files + backup_files:
1404  source_absolute_path = os.path.join(ns3_path, path)
1405  os.remove(source_absolute_path)
1406  if "empty" in path or ".main" in path:
1407  continue
1408  filename = os.path.basename(path).replace(".cc", "")
1409  executable_absolute_path = os.path.dirname(os.path.join(ns3_path, "build", path))
1410  if os.path.exists(executable_absolute_path):
1411  executable_name = list(
1412  filter(lambda x: filename in x, os.listdir(executable_absolute_path))
1413  )[0]
1414 
1415  os.remove(os.path.join(executable_absolute_path, executable_name))
1416  if not os.listdir(os.path.dirname(path)):
1417  os.rmdir(os.path.dirname(source_absolute_path))
1418 
1419  return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1420  self.assertEqual(return_code, 0)
1421 
1423  """!
1424  Test if ns3 is inserting additional arguments by MPICH and OpenMPI to run on the CI
1425  @return None
1426  """
1427  # Skip test if mpi is not installed
1428  if shutil.which("mpiexec") is None or win32:
1429  self.skipTest("Mpi is not available")
1430 
1431  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --enable-examples')
1432  self.assertEqual(return_code, 0)
1433  executables = get_programs_list()
1434 
1435  # Ensure sample simulator was built
1436  return_code, stdout, stderr = run_ns3("build sample-simulator")
1437  self.assertEqual(return_code, 0)
1438 
1439  # Get executable path
1440  sample_simulator_path = list(filter(lambda x: "sample-simulator" in x, executables))[0]
1441 
1442  mpi_command = '--dry-run run sample-simulator --command-template="mpiexec -np 2 %s"'
1443  non_mpi_command = '--dry-run run sample-simulator --command-template="echo %s"'
1444 
1445  # Get the commands to run sample-simulator in two processes with mpi
1446  return_code, stdout, stderr = run_ns3(mpi_command)
1447  self.assertEqual(return_code, 0)
1448  self.assertIn("mpiexec -np 2 %s" % sample_simulator_path, stdout)
1449 
1450  # Get the commands to run sample-simulator in two processes with mpi, now with the environment variable
1451  return_code, stdout, stderr = run_ns3(mpi_command)
1452  self.assertEqual(return_code, 0)
1453  if os.getenv("USER", "") == "root":
1454  if shutil.which("ompi_info"):
1455  self.assertIn(
1456  "mpiexec --allow-run-as-root --oversubscribe -np 2 %s" % sample_simulator_path,
1457  stdout,
1458  )
1459  else:
1460  self.assertIn(
1461  "mpiexec --allow-run-as-root -np 2 %s" % sample_simulator_path, stdout
1462  )
1463  else:
1464  self.assertIn("mpiexec -np 2 %s" % sample_simulator_path, stdout)
1465 
1466  # Now we repeat for the non-mpi command
1467  return_code, stdout, stderr = run_ns3(non_mpi_command)
1468  self.assertEqual(return_code, 0)
1469  self.assertIn("echo %s" % sample_simulator_path, stdout)
1470 
1471  # Again the non-mpi command, with the MPI_CI environment variable set
1472  return_code, stdout, stderr = run_ns3(non_mpi_command)
1473  self.assertEqual(return_code, 0)
1474  self.assertIn("echo %s" % sample_simulator_path, stdout)
1475 
1476  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --disable-examples')
1477  self.assertEqual(return_code, 0)
1478 
1480  """!
1481  Test if CMake and ns3 fail in the expected ways when:
1482  - examples from modules or general examples fail if they depend on a
1483  library with a name shorter than 4 characters or are disabled when
1484  a library is nonexistent
1485  - a module library passes the configuration but fails to build due to
1486  a missing library
1487  @return None
1488  """
1489  os.makedirs("contrib/borked", exist_ok=True)
1490  os.makedirs("contrib/borked/examples", exist_ok=True)
1491 
1492  # Test if configuration succeeds and building the module library fails
1493  with open("contrib/borked/examples/CMakeLists.txt", "w", encoding="utf-8") as f:
1494  f.write("")
1495  for invalid_or_nonexistent_library in ["", "gsd", "lib", "libfi", "calibre"]:
1496  with open("contrib/borked/CMakeLists.txt", "w", encoding="utf-8") as f:
1497  f.write(
1498  """
1499  build_lib(
1500  LIBNAME borked
1501  SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1502  LIBRARIES_TO_LINK ${libcore} %s
1503  )
1504  """
1505  % invalid_or_nonexistent_library
1506  )
1507 
1508  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --enable-examples')
1509  if invalid_or_nonexistent_library in ["", "gsd", "libfi", "calibre"]:
1510  self.assertEqual(return_code, 0)
1511  elif invalid_or_nonexistent_library in ["lib"]:
1512  self.assertEqual(return_code, 1)
1513  self.assertIn("Invalid library name: %s" % invalid_or_nonexistent_library, stderr)
1514  else:
1515  pass
1516 
1517  return_code, stdout, stderr = run_ns3("build borked")
1518  if invalid_or_nonexistent_library in [""]:
1519  self.assertEqual(return_code, 0)
1520  elif invalid_or_nonexistent_library in ["lib"]:
1521  self.assertEqual(return_code, 2) # should fail due to invalid library name
1522  self.assertIn("Invalid library name: %s" % invalid_or_nonexistent_library, stderr)
1523  elif invalid_or_nonexistent_library in ["gsd", "libfi", "calibre"]:
1524  self.assertEqual(return_code, 2) # should fail due to missing library
1525  if "lld" in stdout + stderr:
1526  self.assertIn(
1527  "unable to find library -l%s" % invalid_or_nonexistent_library, stderr
1528  )
1529  elif "mold" in stdout + stderr:
1530  self.assertIn("library not found: %s" % invalid_or_nonexistent_library, stderr)
1531  else:
1532  self.assertIn("cannot find -l%s" % invalid_or_nonexistent_library, stderr)
1533  else:
1534  pass
1535 
1536  # Now test if the example can be built with:
1537  # - no additional library (should work)
1538  # - invalid library names (should fail to configure)
1539  # - valid library names but nonexistent libraries (should not create a target)
1540  with open("contrib/borked/CMakeLists.txt", "w", encoding="utf-8") as f:
1541  f.write(
1542  """
1543  build_lib(
1544  LIBNAME borked
1545  SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1546  LIBRARIES_TO_LINK ${libcore}
1547  )
1548  """
1549  )
1550  for invalid_or_nonexistent_library in ["", "gsd", "lib", "libfi", "calibre"]:
1551  with open("contrib/borked/examples/CMakeLists.txt", "w", encoding="utf-8") as f:
1552  f.write(
1553  """
1554  build_lib_example(
1555  NAME borked-example
1556  SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty-main.cc
1557  LIBRARIES_TO_LINK ${libborked} %s
1558  )
1559  """
1560  % invalid_or_nonexistent_library
1561  )
1562 
1563  return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1564  if invalid_or_nonexistent_library in ["", "gsd", "libfi", "calibre"]:
1565  self.assertEqual(return_code, 0) # should be able to configure
1566  elif invalid_or_nonexistent_library in ["lib"]:
1567  self.assertEqual(return_code, 1) # should fail to even configure
1568  self.assertIn("Invalid library name: %s" % invalid_or_nonexistent_library, stderr)
1569  else:
1570  pass
1571 
1572  return_code, stdout, stderr = run_ns3("build borked-example")
1573  if invalid_or_nonexistent_library in [""]:
1574  self.assertEqual(return_code, 0) # should be able to build
1575  elif invalid_or_nonexistent_library in ["libf"]:
1576  self.assertEqual(return_code, 2) # should fail due to missing configuration
1577  self.assertIn("Invalid library name: %s" % invalid_or_nonexistent_library, stderr)
1578  elif invalid_or_nonexistent_library in ["gsd", "libfi", "calibre"]:
1579  self.assertEqual(return_code, 1) # should fail to find target
1580  self.assertIn("Target to build does not exist: borked-example", stdout)
1581  else:
1582  pass
1583 
1584  shutil.rmtree("contrib/borked", ignore_errors=True)
1585 
1587  """!
1588  Test if CMake can properly handle modules containing "lib",
1589  which is used internally as a prefix for module libraries
1590  @return None
1591  """
1592 
1593  os.makedirs("contrib/calibre", exist_ok=True)
1594  os.makedirs("contrib/calibre/examples", exist_ok=True)
1595 
1596  # Now test if we can have a library with "lib" in it
1597  with open("contrib/calibre/examples/CMakeLists.txt", "w", encoding="utf-8") as f:
1598  f.write("")
1599  with open("contrib/calibre/CMakeLists.txt", "w", encoding="utf-8") as f:
1600  f.write(
1601  """
1602  build_lib(
1603  LIBNAME calibre
1604  SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1605  LIBRARIES_TO_LINK ${libcore}
1606  )
1607  """
1608  )
1609 
1610  return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1611 
1612  # This only checks if configuration passes
1613  self.assertEqual(return_code, 0)
1614 
1615  # This checks if the contrib modules were printed correctly
1616  self.assertIn("calibre", stdout)
1617 
1618  # This checks not only if "lib" from "calibre" was incorrectly removed,
1619  # but also if the pkgconfig file was generated with the correct name
1620  self.assertNotIn("care", stdout)
1621  self.assertTrue(
1622  os.path.exists(os.path.join(ns3_path, "cmake-cache", "pkgconfig", "ns3-calibre.pc"))
1623  )
1624 
1625  # Check if we can build this library
1626  return_code, stdout, stderr = run_ns3("build calibre")
1627  self.assertEqual(return_code, 0)
1628  self.assertIn(cmake_build_target_command(target="libcalibre"), stdout)
1629 
1630  shutil.rmtree("contrib/calibre", ignore_errors=True)
1631 
1633  """!
1634  Test if CMake performance tracing works and produces the
1635  cmake_performance_trace.log file
1636  @return None
1637  """
1638  cmake_performance_trace_log = os.path.join(ns3_path, "cmake_performance_trace.log")
1639  if os.path.exists(cmake_performance_trace_log):
1640  os.remove(cmake_performance_trace_log)
1641 
1642  return_code, stdout, stderr = run_ns3("configure --trace-performance")
1643  self.assertEqual(return_code, 0)
1644  if win32:
1645  self.assertIn("--profiling-format=google-trace --profiling-output=", stdout)
1646  else:
1647  self.assertIn(
1648  "--profiling-format=google-trace --profiling-output=./cmake_performance_trace.log",
1649  stdout,
1650  )
1651  self.assertTrue(os.path.exists(cmake_performance_trace_log))
1652 
1654  """!
1655  Check if ENABLE_BUILD_VERSION and version.cache are working
1656  as expected
1657  @return None
1658  """
1659 
1660  # Create Docker client instance and start it
1661  with DockerContainerManager(self, "ubuntu:22.04") as container:
1662  # Install basic packages
1663  container.execute("apt-get update")
1664  container.execute("apt-get install -y python3 ninja-build cmake g++")
1665 
1666  # Clean ns-3 artifacts
1667  container.execute("./ns3 clean")
1668 
1669  # Set path to version.cache file
1670  version_cache_file = os.path.join(ns3_path, "src/core/model/version.cache")
1671 
1672  # First case: try without a version cache or Git
1673  if os.path.exists(version_cache_file):
1674  os.remove(version_cache_file)
1675 
1676  # We need to catch the exception since the command will fail
1677  try:
1678  container.execute("./ns3 configure -G Ninja --enable-build-version")
1679  except DockerException:
1680  pass
1681  self.assertFalse(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1682 
1683  # Second case: try with a version cache file but without Git (it should succeed)
1684  version_cache_contents = (
1685  "CLOSEST_TAG = '\"ns-3.0.0\"'\n"
1686  "VERSION_COMMIT_HASH = '\"0000000000\"'\n"
1687  "VERSION_DIRTY_FLAG = '0'\n"
1688  "VERSION_MAJOR = '3'\n"
1689  "VERSION_MINOR = '0'\n"
1690  "VERSION_PATCH = '0'\n"
1691  "VERSION_RELEASE_CANDIDATE = '\"\"'\n"
1692  "VERSION_TAG = '\"ns-3.0.0\"'\n"
1693  "VERSION_TAG_DISTANCE = '0'\n"
1694  "VERSION_BUILD_PROFILE = 'debug'\n"
1695  )
1696  with open(version_cache_file, "w", encoding="utf-8") as version:
1697  version.write(version_cache_contents)
1698 
1699  # Configuration should now succeed
1700  container.execute("./ns3 clean")
1701  container.execute("./ns3 configure -G Ninja --enable-build-version")
1702  container.execute("./ns3 build core")
1703  self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1704 
1705  # And contents of version cache should be unchanged
1706  with open(version_cache_file, "r", encoding="utf-8") as version:
1707  self.assertEqual(version.read(), version_cache_contents)
1708 
1709  # Third case: we rename the .git directory temporarily and reconfigure
1710  # to check if it gets configured successfully when Git is found but
1711  # there is not .git history
1712  os.rename(os.path.join(ns3_path, ".git"), os.path.join(ns3_path, "temp_git"))
1713  try:
1714  container.execute("apt-get install -y git")
1715  container.execute("./ns3 clean")
1716  container.execute("./ns3 configure -G Ninja --enable-build-version")
1717  container.execute("./ns3 build core")
1718  except DockerException:
1719  pass
1720  os.rename(os.path.join(ns3_path, "temp_git"), os.path.join(ns3_path, ".git"))
1721  self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1722 
1723  # Fourth case: test with Git and git history. Now the version.cache should be replaced.
1724  container.execute("./ns3 clean")
1725  container.execute("./ns3 configure -G Ninja --enable-build-version")
1726  container.execute("./ns3 build core")
1727  self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1728  with open(version_cache_file, "r", encoding="utf-8") as version:
1729  self.assertNotEqual(version.read(), version_cache_contents)
1730 
1731  # Remove version cache file if it exists
1732  if os.path.exists(version_cache_file):
1733  os.remove(version_cache_file)
1734 
1736  """!
1737  Test filtering in examples and tests from specific modules
1738  @return None
1739  """
1740  # Try filtering enabled modules to core+network and their dependencies
1741  return_code, stdout, stderr = run_ns3(
1742  'configure -G "{generator}" --enable-examples --enable-tests'
1743  )
1744  self.config_okconfig_ok(return_code, stdout, stderr)
1745 
1746  modules_before_filtering = get_enabled_modules()
1747  programs_before_filtering = get_programs_list()
1748 
1749  return_code, stdout, stderr = run_ns3(
1750  "configure -G \"{generator}\" --filter-module-examples-and-tests='core;network'"
1751  )
1752  self.config_okconfig_ok(return_code, stdout, stderr)
1753 
1754  modules_after_filtering = get_enabled_modules()
1755  programs_after_filtering = get_programs_list()
1756 
1757  # At this point we should have the same number of modules
1758  self.assertEqual(len(modules_after_filtering), len(modules_before_filtering))
1759  # But less executables
1760  self.assertLess(len(programs_after_filtering), len(programs_before_filtering))
1761 
1762  # Try filtering in only core
1763  return_code, stdout, stderr = run_ns3(
1764  "configure -G \"{generator}\" --filter-module-examples-and-tests='core'"
1765  )
1766  self.config_okconfig_ok(return_code, stdout, stderr)
1767 
1768  # At this point we should have the same number of modules
1769  self.assertEqual(len(get_enabled_modules()), len(modules_after_filtering))
1770  # But less executables
1771  self.assertLess(len(get_programs_list()), len(programs_after_filtering))
1772 
1773  # Try cleaning the list of enabled modules to reset to the normal configuration.
1774  return_code, stdout, stderr = run_ns3(
1775  "configure -G \"{generator}\" --disable-examples --disable-tests --filter-module-examples-and-tests=''"
1776  )
1777  self.config_okconfig_ok(return_code, stdout, stderr)
1778 
1779  # At this point we should have the same amount of modules that we had when we started.
1780  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
1781  self.assertEqual(len(get_programs_list()), len(self.ns3_executablesns3_executables))
1782 
1784  """!
1785  Check if fast linkers LLD and Mold are correctly found and configured
1786  @return None
1787  """
1788 
1789  run_ns3("clean")
1790  with DockerContainerManager(self, "gcc:12.1") as container:
1791  # Install basic packages
1792  container.execute("apt-get update")
1793  container.execute("apt-get install -y python3 ninja-build cmake g++ lld")
1794 
1795  # Configure should detect and use lld
1796  container.execute("./ns3 configure -G Ninja")
1797 
1798  # Check if configuration properly detected lld
1799  self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1800  with open(
1801  os.path.join(ns3_path, "cmake-cache", "build.ninja"), "r", encoding="utf-8"
1802  ) as f:
1803  self.assertIn("-fuse-ld=lld", f.read())
1804 
1805  # Try to build using the lld linker
1806  try:
1807  container.execute("./ns3 build core")
1808  except DockerException:
1809  self.assertTrue(False, "Build with lld failed")
1810 
1811  # Now add mold to the PATH
1812  if not os.path.exists("./mold-1.4.2-x86_64-linux.tar.gz"):
1813  container.execute(
1814  "wget https://github.com/rui314/mold/releases/download/v1.4.2/mold-1.4.2-x86_64-linux.tar.gz"
1815  )
1816  container.execute(
1817  "tar xzfC mold-1.4.2-x86_64-linux.tar.gz /usr/local --strip-components=1"
1818  )
1819 
1820  # Configure should detect and use mold
1821  run_ns3("clean")
1822  container.execute("./ns3 configure -G Ninja")
1823 
1824  # Check if configuration properly detected mold
1825  self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1826  with open(
1827  os.path.join(ns3_path, "cmake-cache", "build.ninja"), "r", encoding="utf-8"
1828  ) as f:
1829  self.assertIn("-fuse-ld=mold", f.read())
1830 
1831  # Try to build using the lld linker
1832  try:
1833  container.execute("./ns3 build core")
1834  except DockerException:
1835  self.assertTrue(False, "Build with mold failed")
1836 
1837  # Delete mold leftovers
1838  os.remove("./mold-1.4.2-x86_64-linux.tar.gz")
1839 
1840  # Disable use of fast linkers
1841  container.execute("./ns3 configure -G Ninja -- -DNS3_FAST_LINKERS=OFF")
1842 
1843  # Check if configuration properly disabled lld/mold usage
1844  self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1845  with open(
1846  os.path.join(ns3_path, "cmake-cache", "build.ninja"), "r", encoding="utf-8"
1847  ) as f:
1848  self.assertNotIn("-fuse-ld=mold", f.read())
1849 
1851  """!
1852  Check if NS3_CLANG_TIMETRACE feature is working
1853  Clang's -ftime-trace plus ClangAnalyzer report
1854  @return None
1855  """
1856 
1857  run_ns3("clean")
1858  with DockerContainerManager(self, "ubuntu:20.04") as container:
1859  container.execute("apt-get update")
1860  container.execute("apt-get install -y python3 ninja-build cmake clang-10")
1861 
1862  # Enable ClangTimeTrace without git (it should fail)
1863  try:
1864  container.execute(
1865  "./ns3 configure -G Ninja --enable-modules=core --enable-examples --enable-tests -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-10 -DNS3_CLANG_TIMETRACE=ON"
1866  )
1867  except DockerException as e:
1868  self.assertIn("could not find git for clone of ClangBuildAnalyzer", e.stderr)
1869 
1870  container.execute("apt-get install -y git")
1871 
1872  # Enable ClangTimeTrace without git (it should succeed)
1873  try:
1874  container.execute(
1875  "./ns3 configure -G Ninja --enable-modules=core --enable-examples --enable-tests -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-10 -DNS3_CLANG_TIMETRACE=ON"
1876  )
1877  except DockerException as e:
1878  self.assertIn("could not find git for clone of ClangBuildAnalyzer", e.stderr)
1879 
1880  # Clean leftover time trace report
1881  time_trace_report_path = os.path.join(ns3_path, "ClangBuildAnalyzerReport.txt")
1882  if os.path.exists(time_trace_report_path):
1883  os.remove(time_trace_report_path)
1884 
1885  # Build new time trace report
1886  try:
1887  container.execute("./ns3 build timeTraceReport")
1888  except DockerException as e:
1889  self.assertTrue(False, "Failed to build the ClangAnalyzer's time trace report")
1890 
1891  # Check if the report exists
1892  self.assertTrue(os.path.exists(time_trace_report_path))
1893 
1894  # Now try with GCC, which should fail during the configuration
1895  run_ns3("clean")
1896  container.execute("apt-get install -y g++")
1897  container.execute("apt-get remove -y clang-10")
1898 
1899  try:
1900  container.execute(
1901  "./ns3 configure -G Ninja --enable-modules=core --enable-examples --enable-tests -- -DNS3_CLANG_TIMETRACE=ON"
1902  )
1903  self.assertTrue(
1904  False, "ClangTimeTrace requires Clang, but GCC just passed the checks too"
1905  )
1906  except DockerException as e:
1907  self.assertIn("TimeTrace is a Clang feature", e.stderr)
1908 
1910  """!
1911  Check if NS3_NINJA_TRACE feature is working
1912  Ninja's .ninja_log conversion to about://tracing
1913  json format conversion with Ninjatracing
1914  @return None
1915  """
1916 
1917  run_ns3("clean")
1918  with DockerContainerManager(self, "ubuntu:20.04") as container:
1919  container.execute("apt-get update")
1920  container.execute("apt-get install -y python3 cmake clang-10")
1921 
1922  # Enable Ninja tracing without using the Ninja generator
1923  try:
1924  container.execute(
1925  "./ns3 configure --enable-modules=core --enable-ninja-tracing -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-10"
1926  )
1927  except DockerException as e:
1928  self.assertIn("Ninjatracing requires the Ninja generator", e.stderr)
1929 
1930  # Clean build system leftovers
1931  run_ns3("clean")
1932 
1933  container.execute("apt-get install -y ninja-build")
1934  # Enable Ninjatracing support without git (should fail)
1935  try:
1936  container.execute(
1937  "./ns3 configure -G Ninja --enable-modules=core --enable-ninja-tracing -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-10"
1938  )
1939  except DockerException as e:
1940  self.assertIn("could not find git for clone of NinjaTracing", e.stderr)
1941 
1942  container.execute("apt-get install -y git")
1943  # Enable Ninjatracing support with git (it should succeed)
1944  try:
1945  container.execute(
1946  "./ns3 configure -G Ninja --enable-modules=core --enable-ninja-tracing -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-10"
1947  )
1948  except DockerException as e:
1949  self.assertTrue(False, "Failed to configure with Ninjatracing")
1950 
1951  # Clean leftover ninja trace
1952  ninja_trace_path = os.path.join(ns3_path, "ninja_performance_trace.json")
1953  if os.path.exists(ninja_trace_path):
1954  os.remove(ninja_trace_path)
1955 
1956  # Build the core module
1957  container.execute("./ns3 build core")
1958 
1959  # Build new ninja trace
1960  try:
1961  container.execute("./ns3 build ninjaTrace")
1962  except DockerException as e:
1963  self.assertTrue(False, "Failed to run Ninjatracing's tool to build the trace")
1964 
1965  # Check if the report exists
1966  self.assertTrue(os.path.exists(ninja_trace_path))
1967  trace_size = os.stat(ninja_trace_path).st_size
1968  os.remove(ninja_trace_path)
1969 
1970  run_ns3("clean")
1971 
1972  # Enable Clang TimeTrace feature for more detailed traces
1973  try:
1974  container.execute(
1975  "./ns3 configure -G Ninja --enable-modules=core --enable-ninja-tracing -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-10 -DNS3_CLANG_TIMETRACE=ON"
1976  )
1977  except DockerException as e:
1978  self.assertTrue(False, "Failed to configure Ninjatracing with Clang's TimeTrace")
1979 
1980  # Build the core module
1981  container.execute("./ns3 build core")
1982 
1983  # Build new ninja trace
1984  try:
1985  container.execute("./ns3 build ninjaTrace")
1986  except DockerException as e:
1987  self.assertTrue(False, "Failed to run Ninjatracing's tool to build the trace")
1988 
1989  self.assertTrue(os.path.exists(ninja_trace_path))
1990  timetrace_size = os.stat(ninja_trace_path).st_size
1991  os.remove(ninja_trace_path)
1992 
1993  # Check if timetrace's trace is bigger than the original trace (it should be)
1994  self.assertGreater(timetrace_size, trace_size)
1995 
1997  """!
1998  Check if precompiled headers are being enabled correctly.
1999  @return None
2000  """
2001 
2002  run_ns3("clean")
2003 
2004  # Ubuntu 20.04 ships with:
2005  # - cmake 3.16: does support PCH
2006  # - ccache 3.7: incompatible with pch
2007  with DockerContainerManager(self, "ubuntu:20.04") as container:
2008  container.execute("apt-get update")
2009  container.execute("apt-get install -y python3 cmake ccache g++")
2010  try:
2011  container.execute("./ns3 configure")
2012  except DockerException as e:
2013  self.assertIn("incompatible with ccache", e.stderr)
2014  run_ns3("clean")
2015 
2016  # Ubuntu 22.04 ships with:
2017  # - cmake 3.22: does support PCH
2018  # - ccache 4.5: compatible with pch
2019  with DockerContainerManager(self, "ubuntu:22.04") as container:
2020  container.execute("apt-get update")
2021  container.execute("apt-get install -y python3 cmake ccache g++")
2022  try:
2023  container.execute("./ns3 configure")
2024  except DockerException as e:
2025  self.assertTrue(False, "Precompiled headers should have been enabled")
2026 
2028  """!
2029  Check for regressions in test object build.
2030  @return None
2031  """
2032  return_code, stdout, stderr = run_ns3("configure")
2033  self.assertEqual(return_code, 0)
2034 
2035  test_module_cache = os.path.join(ns3_path, "cmake-cache", "src", "test")
2036  self.assertFalse(os.path.exists(test_module_cache))
2037 
2038  return_code, stdout, stderr = run_ns3("configure --enable-tests")
2039  self.assertEqual(return_code, 0)
2040  self.assertTrue(os.path.exists(test_module_cache))
2041 
2043  """!
2044  Check for regressions in a bare ns-3 configuration.
2045  @return None
2046  """
2047 
2048  run_ns3("clean")
2049 
2050  with DockerContainerManager(self, "ubuntu:20.04") as container:
2051  container.execute("apt-get update")
2052  container.execute("apt-get install -y python3 cmake g++")
2053  return_code = 0
2054  stdout = ""
2055  try:
2056  stdout = container.execute("./ns3 configure -d release")
2057  except DockerException as e:
2058  return_code = 1
2059  self.config_okconfig_ok(return_code, stdout, stdout)
2060 
2061  run_ns3("clean")
2062 
2063 
2065  """!
2066  Tests ns3 regarding building the project
2067  """
2068 
2069  def setUp(self):
2070  """!
2071  Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
2072  @return None
2073  """
2074  super().setUp()
2075 
2076  self.ns3_librariesns3_libraries = get_libraries_list()
2077 
2079  """!
2080  Try building the core library
2081  @return None
2082  """
2083  return_code, stdout, stderr = run_ns3("build core")
2084  self.assertEqual(return_code, 0)
2085  self.assertIn("Built target libcore", stdout)
2086 
2088  """!
2089  Try building core-test library without tests enabled
2090  @return None
2091  """
2092  # tests are not enabled, so the target isn't available
2093  return_code, stdout, stderr = run_ns3("build core-test")
2094  self.assertEqual(return_code, 1)
2095  self.assertIn("Target to build does not exist: core-test", stdout)
2096 
2098  """!
2099  Try building the project:
2100  @return None
2101  """
2102  return_code, stdout, stderr = run_ns3("build")
2103  self.assertEqual(return_code, 0)
2104  self.assertIn("Built target", stdout)
2105  for program in get_programs_list():
2106  self.assertTrue(os.path.exists(program), program)
2107  self.assertIn(cmake_build_project_command, stdout)
2108 
2110  """!
2111  Try hiding task lines
2112  @return None
2113  """
2114  return_code, stdout, stderr = run_ns3("--quiet build")
2115  self.assertEqual(return_code, 0)
2116  self.assertIn(cmake_build_project_command, stdout)
2117 
2119  """!
2120  Try removing an essential file to break the build
2121  @return None
2122  """
2123  # change an essential file to break the build.
2124  attribute_cc_path = os.sep.join([ns3_path, "src", "core", "model", "attribute.cc"])
2125  attribute_cc_bak_path = attribute_cc_path + ".bak"
2126  shutil.move(attribute_cc_path, attribute_cc_bak_path)
2127 
2128  # build should break.
2129  return_code, stdout, stderr = run_ns3("build")
2130  self.assertNotEqual(return_code, 0)
2131 
2132  # move file back.
2133  shutil.move(attribute_cc_bak_path, attribute_cc_path)
2134 
2135  # Build should work again.
2136  return_code, stdout, stderr = run_ns3("build")
2137  self.assertEqual(return_code, 0)
2138 
2140  """!
2141  Test if changing the version file affects the library names
2142  @return None
2143  """
2144  run_ns3("build")
2145  self.ns3_librariesns3_libraries = get_libraries_list()
2146 
2147  version_file = os.sep.join([ns3_path, "VERSION"])
2148  with open(version_file, "w", encoding="utf-8") as f:
2149  f.write("3-00\n")
2150 
2151  # Reconfigure.
2152  return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
2153  self.config_okconfig_ok(return_code, stdout, stderr)
2154 
2155  # Build.
2156  return_code, stdout, stderr = run_ns3("build")
2157  self.assertEqual(return_code, 0)
2158  self.assertIn("Built target", stdout)
2159 
2160  # Programs with new versions.
2161  new_programs = get_programs_list()
2162 
2163  # Check if they exist.
2164  for program in new_programs:
2165  self.assertTrue(os.path.exists(program))
2166 
2167  # Check if we still have the same number of binaries.
2168  self.assertEqual(len(new_programs), len(self.ns3_executablesns3_executablesns3_executables))
2169 
2170  # Check if versions changed from 3-dev to 3-00.
2171  libraries = get_libraries_list()
2172  new_libraries = list(set(libraries).difference(set(self.ns3_librariesns3_libraries)))
2173  self.assertEqual(len(new_libraries), len(self.ns3_librariesns3_libraries))
2174  for library in new_libraries:
2175  self.assertNotIn("libns3-dev", library)
2176  self.assertIn("libns3-00", library)
2177  self.assertTrue(os.path.exists(library))
2178 
2179  # Restore version file.
2180  with open(version_file, "w", encoding="utf-8") as f:
2181  f.write("3-dev\n")
2182 
2184  """!
2185  Try setting a different output directory and if everything is
2186  in the right place and still working correctly
2187  @return None
2188  """
2189 
2190  # Re-build to return to the original state.
2191  return_code, stdout, stderr = run_ns3("build")
2192  self.assertEqual(return_code, 0)
2193 
2194 
2195  self.ns3_librariesns3_libraries = get_libraries_list()
2196 
2197 
2199 
2200  # Delete built programs and libraries to check if they were restored later.
2201  for program in self.ns3_executablesns3_executablesns3_executables:
2202  os.remove(program)
2203  for library in self.ns3_librariesns3_libraries:
2204  os.remove(library)
2205 
2206  # Reconfigure setting the output folder to ns-3-dev/build/release (both as an absolute path or relative).
2207  absolute_path = os.sep.join([ns3_path, "build", "release"])
2208  relative_path = os.sep.join(["build", "release"])
2209  for different_out_dir in [absolute_path, relative_path]:
2210  return_code, stdout, stderr = run_ns3(
2211  'configure -G "{generator}" --out=%s' % different_out_dir
2212  )
2213  self.config_okconfig_ok(return_code, stdout, stderr)
2214  self.assertIn(
2215  "Build directory : %s" % absolute_path.replace(os.sep, "/"), stdout
2216  )
2217 
2218  # Build
2219  run_ns3("build")
2220 
2221  # Check if we have the same number of binaries and that they were built correctly.
2222  new_programs = get_programs_list()
2223  self.assertEqual(len(new_programs), len(self.ns3_executablesns3_executablesns3_executables))
2224  for program in new_programs:
2225  self.assertTrue(os.path.exists(program))
2226 
2227  # Check if we have the same number of libraries and that they were built correctly.
2228  libraries = get_libraries_list(os.sep.join([absolute_path, "lib"]))
2229  new_libraries = list(set(libraries).difference(set(self.ns3_librariesns3_libraries)))
2230  self.assertEqual(len(new_libraries), len(self.ns3_librariesns3_libraries))
2231  for library in new_libraries:
2232  self.assertTrue(os.path.exists(library))
2233 
2234  # Remove files in the different output dir.
2235  shutil.rmtree(absolute_path)
2236 
2237  # Restore original output directory.
2238  return_code, stdout, stderr = run_ns3("configure -G \"{generator}\" --out=''")
2239  self.config_okconfig_ok(return_code, stdout, stderr)
2240  self.assertIn(
2241  "Build directory : %s" % usual_outdir.replace(os.sep, "/"), stdout
2242  )
2243 
2244  # Try re-building.
2245  run_ns3("build")
2246 
2247  # Check if we have the same binaries we had at the beginning.
2248  new_programs = get_programs_list()
2249  self.assertEqual(len(new_programs), len(self.ns3_executablesns3_executablesns3_executables))
2250  for program in new_programs:
2251  self.assertTrue(os.path.exists(program))
2252 
2253  # Check if we have the same libraries we had at the beginning.
2254  libraries = get_libraries_list()
2255  self.assertEqual(len(libraries), len(self.ns3_librariesns3_libraries))
2256  for library in libraries:
2257  self.assertTrue(os.path.exists(library))
2258 
2260  """!
2261  Tries setting a ns3 version, then installing it.
2262  After that, tries searching for ns-3 with CMake's find_package(ns3).
2263  Finally, tries using core library in a 3rd-party project
2264  @return None
2265  """
2266  # Remove existing libraries from the previous step.
2267  libraries = get_libraries_list()
2268  for library in libraries:
2269  os.remove(library)
2270 
2271  # 3-dev version format is not supported by CMake, so we use 3.01.
2272  version_file = os.sep.join([ns3_path, "VERSION"])
2273  with open(version_file, "w", encoding="utf-8") as f:
2274  f.write("3-01\n")
2275 
2276  # Reconfigure setting the installation folder to ns-3-dev/build/install.
2277  install_prefix = os.sep.join([ns3_path, "build", "install"])
2278  return_code, stdout, stderr = run_ns3(
2279  'configure -G "{generator}" --prefix=%s' % install_prefix
2280  )
2281  self.config_okconfig_ok(return_code, stdout, stderr)
2282 
2283  # Build.
2284  run_ns3("build")
2285  libraries = get_libraries_list()
2286  headers = get_headers_list()
2287 
2288  # Install.
2289  run_ns3("install")
2290 
2291  # Find out if libraries were installed to lib or lib64 (Fedora thing).
2292  lib64 = os.path.exists(os.sep.join([install_prefix, "lib64"]))
2293  installed_libdir = os.sep.join([install_prefix, ("lib64" if lib64 else "lib")])
2294 
2295  # Make sure all libraries were installed.
2296  installed_libraries = get_libraries_list(installed_libdir)
2297  installed_libraries_list = ";".join(installed_libraries)
2298  for library in libraries:
2299  library_name = os.path.basename(library)
2300  self.assertIn(library_name, installed_libraries_list)
2301 
2302  # Make sure all headers were installed.
2303  installed_headers = get_headers_list(install_prefix)
2304  missing_headers = list(
2305  set([os.path.basename(x) for x in headers])
2306  - (set([os.path.basename(x) for x in installed_headers]))
2307  )
2308  self.assertEqual(len(missing_headers), 0)
2309 
2310  # Now create a test CMake project and try to find_package ns-3.
2311  test_main_file = os.sep.join([install_prefix, "main.cpp"])
2312  with open(test_main_file, "w", encoding="utf-8") as f:
2313  f.write(
2314  """
2315  #include <ns3/core-module.h>
2316  using namespace ns3;
2317  int main ()
2318  {
2319  Simulator::Stop (Seconds (1.0));
2320  Simulator::Run ();
2321  Simulator::Destroy ();
2322  return 0;
2323  }
2324  """
2325  )
2326 
2327  # We try to use this library without specifying a version,
2328  # specifying ns3-01 (text version with 'dev' is not supported)
2329  # and specifying ns3-00 (a wrong version)
2330  for version in ["", "3.01", "3.00"]:
2331  ns3_import_methods = []
2332 
2333  # Import ns-3 libraries with as a CMake package
2334  cmake_find_package_import = """
2335  list(APPEND CMAKE_PREFIX_PATH ./{lib}/cmake/ns3)
2336  find_package(ns3 {version} COMPONENTS libcore)
2337  target_link_libraries(test PRIVATE ns3::libcore)
2338  """.format(
2339  lib=("lib64" if lib64 else "lib"), version=version
2340  )
2341  ns3_import_methods.append(cmake_find_package_import)
2342 
2343  # Import ns-3 as pkg-config libraries
2344  pkgconfig_import = """
2345  list(APPEND CMAKE_PREFIX_PATH ./)
2346  include(FindPkgConfig)
2347  pkg_check_modules(ns3 REQUIRED IMPORTED_TARGET ns3-core{version})
2348  target_link_libraries(test PUBLIC PkgConfig::ns3)
2349  """.format(
2350  lib=("lib64" if lib64 else "lib"), version="=" + version if version else ""
2351  )
2352  if shutil.which("pkg-config"):
2353  ns3_import_methods.append(pkgconfig_import)
2354 
2355  # Test the multiple ways of importing ns-3 libraries
2356  for import_method in ns3_import_methods:
2357  test_cmake_project = (
2358  """
2359  cmake_minimum_required(VERSION 3.12..3.12)
2360  project(ns3_consumer CXX)
2361  set(CMAKE_CXX_STANDARD 20)
2362  set(CMAKE_CXX_STANDARD_REQUIRED ON)
2363  add_executable(test main.cpp)
2364  """
2365  + import_method
2366  )
2367 
2368  test_cmake_project_file = os.sep.join([install_prefix, "CMakeLists.txt"])
2369  with open(test_cmake_project_file, "w", encoding="utf-8") as f:
2370  f.write(test_cmake_project)
2371 
2372  # Configure the test project
2373  cmake = shutil.which("cmake")
2374  return_code, stdout, stderr = run_program(
2375  cmake,
2376  '-DCMAKE_BUILD_TYPE=debug -G"{generator}" .'.format(
2377  generator=platform_makefiles
2378  ),
2379  cwd=install_prefix,
2380  )
2381 
2382  if version == "3.00":
2383  self.assertEqual(return_code, 1)
2384  if import_method == cmake_find_package_import:
2385  self.assertIn(
2386  'Could not find a configuration file for package "ns3" that is compatible',
2387  stderr.replace("\n", ""),
2388  )
2389  elif import_method == pkgconfig_import:
2390  self.assertIn("A required package was not found", stderr.replace("\n", ""))
2391  else:
2392  raise Exception("Unknown import type")
2393  else:
2394  self.assertEqual(return_code, 0)
2395  self.assertIn("Build files", stdout)
2396 
2397  # Build the test project making use of import ns-3
2398  return_code, stdout, stderr = run_program("cmake", "--build .", cwd=install_prefix)
2399 
2400  if version == "3.00":
2401  self.assertEqual(return_code, 2)
2402  self.assertGreater(len(stderr), 0)
2403  else:
2404  self.assertEqual(return_code, 0)
2405  self.assertIn("Built target", stdout)
2406 
2407  # Try running the test program that imports ns-3
2408  if win32:
2409  test_program = os.path.join(install_prefix, "test.exe")
2410  env_sep = ";" if ";" in os.environ["PATH"] else ":"
2411  env = {
2412  "PATH": env_sep.join(
2413  [os.environ["PATH"], os.path.join(install_prefix, "lib")]
2414  )
2415  }
2416  else:
2417  test_program = "./test"
2418  env = None
2419  return_code, stdout, stderr = run_program(
2420  test_program, "", cwd=install_prefix, env=env
2421  )
2422  self.assertEqual(return_code, 0)
2423 
2424  # Uninstall
2425  return_code, stdout, stderr = run_ns3("uninstall")
2426  self.assertIn("Built target uninstall", stdout)
2427 
2428  # Restore 3-dev version file
2429  os.remove(version_file)
2430  with open(version_file, "w", encoding="utf-8") as f:
2431  f.write("3-dev\n")
2432 
2434  """!
2435  Tries to build scratch-simulator and subdir/scratch-simulator-subdir
2436  @return None
2437  """
2438  # Build.
2439  targets = {
2440  "scratch/scratch-simulator": "scratch-simulator",
2441  "scratch/scratch-simulator.cc": "scratch-simulator",
2442  "scratch-simulator": "scratch-simulator",
2443  "scratch/subdir/scratch-subdir": "subdir_scratch-subdir",
2444  "subdir/scratch-subdir": "subdir_scratch-subdir",
2445  "scratch-subdir": "subdir_scratch-subdir",
2446  }
2447  for target_to_run, target_cmake in targets.items():
2448  # Test if build is working.
2449  build_line = "target scratch_%s" % target_cmake
2450  return_code, stdout, stderr = run_ns3("build %s" % target_to_run)
2451  self.assertEqual(return_code, 0)
2452  self.assertIn(build_line, stdout)
2453 
2454  # Test if run is working
2455  return_code, stdout, stderr = run_ns3("run %s --verbose" % target_to_run)
2456  self.assertEqual(return_code, 0)
2457  self.assertIn(build_line, stdout)
2458  stdout = stdout.replace("scratch_%s" % target_cmake, "") # remove build lines
2459  self.assertIn(target_to_run.split("/")[-1].replace(".cc", ""), stdout)
2460 
2462  """!
2463  Test if ns3 can alert correctly in case a shortcut collision happens
2464  @return None
2465  """
2466 
2467  # First enable examples
2468  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --enable-examples')
2469  self.assertEqual(return_code, 0)
2470 
2471  # Copy second.cc from the tutorial examples to the scratch folder
2472  shutil.copy("./examples/tutorial/second.cc", "./scratch/second.cc")
2473 
2474  # Reconfigure to re-scan the scratches
2475  return_code, stdout, stderr = run_ns3('configure -G "{generator}" --enable-examples')
2476  self.assertEqual(return_code, 0)
2477 
2478  # Try to run second and collide
2479  return_code, stdout, stderr = run_ns3("build second")
2480  self.assertEqual(return_code, 1)
2481  self.assertIn(
2482  'Build target "second" is ambiguous. Try one of these: "scratch/second", "examples/tutorial/second"',
2483  stdout.replace(os.sep, "/"),
2484  )
2485 
2486  # Try to run scratch/second and succeed
2487  return_code, stdout, stderr = run_ns3("build scratch/second")
2488  self.assertEqual(return_code, 0)
2489  self.assertIn(cmake_build_target_command(target="scratch_second"), stdout)
2490 
2491  # Try to run scratch/second and succeed
2492  return_code, stdout, stderr = run_ns3("build tutorial/second")
2493  self.assertEqual(return_code, 0)
2494  self.assertIn(cmake_build_target_command(target="second"), stdout)
2495 
2496  # Try to run second and collide
2497  return_code, stdout, stderr = run_ns3("run second")
2498  self.assertEqual(return_code, 1)
2499  self.assertIn(
2500  'Run target "second" is ambiguous. Try one of these: "scratch/second", "examples/tutorial/second"',
2501  stdout.replace(os.sep, "/"),
2502  )
2503 
2504  # Try to run scratch/second and succeed
2505  return_code, stdout, stderr = run_ns3("run scratch/second")
2506  self.assertEqual(return_code, 0)
2507 
2508  # Try to run scratch/second and succeed
2509  return_code, stdout, stderr = run_ns3("run tutorial/second")
2510  self.assertEqual(return_code, 0)
2511 
2512  # Remove second
2513  os.remove("./scratch/second.cc")
2514 
2516  """!
2517  Test if we can build a static ns-3 library and link it to static programs
2518  @return None
2519  """
2520 
2521  # First enable examples and static build
2522  return_code, stdout, stderr = run_ns3(
2523  'configure -G "{generator}" --enable-examples --disable-gtk --enable-static'
2524  )
2525 
2526  if win32:
2527  # Configuration should fail explaining Windows
2528  # socket libraries cannot be statically linked
2529  self.assertEqual(return_code, 1)
2530  self.assertIn("Static builds are unsupported on Windows", stderr)
2531  else:
2532  # If configuration passes, we are half way done
2533  self.assertEqual(return_code, 0)
2534 
2535  # Then try to build one example
2536  return_code, stdout, stderr = run_ns3("build sample-simulator")
2537  self.assertEqual(return_code, 0)
2538  self.assertIn("Built target", stdout)
2539 
2540  # Maybe check the built binary for shared library references? Using objdump, otool, etc
2541 
2543  """!
2544  Test if we can use python bindings
2545  @return None
2546  """
2547  try:
2548  import cppyy
2549  except ModuleNotFoundError:
2550  self.skipTest("Cppyy was not found")
2551 
2552  # First enable examples and static build
2553  return_code, stdout, stderr = run_ns3(
2554  'configure -G "{generator}" --enable-examples --enable-python-bindings'
2555  )
2556 
2557  # If configuration passes, we are half way done
2558  self.assertEqual(return_code, 0)
2559 
2560  # Then build and run tests
2561  return_code, stdout, stderr = run_program("test.py", "", python=True)
2562  self.assertEqual(return_code, 0)
2563 
2564  # Then try to run a specific test
2565  return_code, stdout, stderr = run_program("test.py", "-p mixed-wired-wireless", python=True)
2566  self.assertEqual(return_code, 0)
2567 
2568  # Then try to run a specific test with the full relative path
2569  return_code, stdout, stderr = run_program(
2570  "test.py", "-p ./examples/wireless/mixed-wired-wireless", python=True
2571  )
2572  self.assertEqual(return_code, 0)
2573 
2575  """!
2576  Test if we had regressions with brite, click and openflow modules
2577  that depend on homonymous libraries
2578  @return None
2579  """
2580  if shutil.which("git") is None:
2581  self.skipTest("Missing git")
2582 
2583  # First enable automatic components fetching
2584  return_code, stdout, stderr = run_ns3("configure -- -DNS3_FETCH_OPTIONAL_COMPONENTS=ON")
2585  self.assertEqual(return_code, 0)
2586 
2587  # Build the optional components to check if their dependencies were fetched
2588  # and there were no build regressions
2589  return_code, stdout, stderr = run_ns3("build brite click openflow")
2590  self.assertEqual(return_code, 0)
2591 
2593  """!
2594  Test if we can link contrib modules to src modules
2595  @return None
2596  """
2597  if shutil.which("git") is None:
2598  self.skipTest("Missing git")
2599 
2600  destination_contrib = os.path.join(ns3_path, "contrib/test-contrib-dependency")
2601  destination_src = os.path.join(ns3_path, "src/test-src-dependent-on-contrib")
2602  # Remove pre-existing directories
2603  if os.path.exists(destination_contrib):
2604  shutil.rmtree(destination_contrib)
2605  if os.path.exists(destination_src):
2606  shutil.rmtree(destination_src)
2607 
2608  # Always use a fresh copy
2609  shutil.copytree(
2610  os.path.join(ns3_path, "build-support/test-files/test-contrib-dependency"),
2611  destination_contrib,
2612  )
2613  shutil.copytree(
2614  os.path.join(ns3_path, "build-support/test-files/test-src-dependent-on-contrib"),
2615  destination_src,
2616  )
2617 
2618  # Then configure
2619  return_code, stdout, stderr = run_ns3("configure --enable-examples")
2620  self.assertEqual(return_code, 0)
2621 
2622  # Build the src module that depend on a contrib module
2623  return_code, stdout, stderr = run_ns3("run source-example")
2624  self.assertEqual(return_code, 0)
2625 
2626  # Remove module copies
2627  shutil.rmtree(destination_contrib)
2628  shutil.rmtree(destination_src)
2629 
2630 
2632  """!
2633  Tests ns3 usage in more realistic scenarios
2634  """
2635 
2636  def setUp(self):
2637  """!
2638  Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
2639  Here examples, tests and documentation are also enabled.
2640  @return None
2641  """
2642 
2643  super().setUp()
2644 
2645  # On top of the release build configured by NS3ConfigureTestCase, also enable examples, tests and docs.
2646  return_code, stdout, stderr = run_ns3(
2647  'configure -d release -G "{generator}" --enable-examples --enable-tests'
2648  )
2649  self.config_okconfig_ok(return_code, stdout, stderr)
2650 
2651  # Check if .lock-ns3 exists, then read to get list of executables.
2652  self.assertTrue(os.path.exists(ns3_lock_filename))
2653 
2654 
2656 
2657  # Check if .lock-ns3 exists than read to get the list of enabled modules.
2658  self.assertTrue(os.path.exists(ns3_lock_filename))
2659 
2660 
2662 
2664  """!
2665  Try to build the project
2666  @return None
2667  """
2668  return_code, stdout, stderr = run_ns3("build")
2669  self.assertEqual(return_code, 0)
2670  self.assertIn("Built target", stdout)
2671  for program in get_programs_list():
2672  self.assertTrue(os.path.exists(program))
2673  libraries = get_libraries_list()
2674  for module in get_enabled_modules():
2675  self.assertIn(module.replace("ns3-", ""), ";".join(libraries))
2676  self.assertIn(cmake_build_project_command, stdout)
2677 
2679  """!
2680  Try to build and run test-runner
2681  @return None
2682  """
2683  return_code, stdout, stderr = run_ns3('run "test-runner --list" --verbose')
2684  self.assertEqual(return_code, 0)
2685  self.assertIn("Built target test-runner", stdout)
2686  self.assertIn(cmake_build_target_command(target="test-runner"), stdout)
2687 
2689  """!
2690  Try to build and run a library
2691  @return None
2692  """
2693  return_code, stdout, stderr = run_ns3("run core") # this should not work
2694  self.assertEqual(return_code, 1)
2695  self.assertIn("Couldn't find the specified program: core", stderr)
2696 
2698  """!
2699  Try to build and run an unknown target
2700  @return None
2701  """
2702  return_code, stdout, stderr = run_ns3("run nonsense") # this should not work
2703  self.assertEqual(return_code, 1)
2704  self.assertIn("Couldn't find the specified program: nonsense", stderr)
2705 
2707  """!
2708  Try to run test-runner without building
2709  @return None
2710  """
2711  return_code, stdout, stderr = run_ns3("build test-runner")
2712  self.assertEqual(return_code, 0)
2713 
2714  return_code, stdout, stderr = run_ns3('run "test-runner --list" --no-build --verbose')
2715  self.assertEqual(return_code, 0)
2716  self.assertNotIn("Built target test-runner", stdout)
2717  self.assertNotIn(cmake_build_target_command(target="test-runner"), stdout)
2718 
2720  """!
2721  Test ns3 fails to run a library
2722  @return None
2723  """
2724  return_code, stdout, stderr = run_ns3("run core --no-build") # this should not work
2725  self.assertEqual(return_code, 1)
2726  self.assertIn("Couldn't find the specified program: core", stderr)
2727 
2729  """!
2730  Test ns3 fails to run an unknown program
2731  @return None
2732  """
2733  return_code, stdout, stderr = run_ns3("run nonsense --no-build") # this should not work
2734  self.assertEqual(return_code, 1)
2735  self.assertIn("Couldn't find the specified program: nonsense", stderr)
2736 
2738  """!
2739  Test if scratch simulator is executed through gdb and lldb
2740  @return None
2741  """
2742  if shutil.which("gdb") is None:
2743  self.skipTest("Missing gdb")
2744 
2745  return_code, stdout, stderr = run_ns3("build scratch-simulator")
2746  self.assertEqual(return_code, 0)
2747 
2748  return_code, stdout, stderr = run_ns3(
2749  "run scratch-simulator --gdb --verbose --no-build", env={"gdb_eval": "1"}
2750  )
2751  self.assertEqual(return_code, 0)
2752  self.assertIn("scratch-simulator", stdout)
2753  if win32:
2754  self.assertIn("GNU gdb", stdout)
2755  else:
2756  self.assertIn("No debugging symbols found", stdout)
2757 
2759  """!
2760  Test if scratch simulator is executed through valgrind
2761  @return None
2762  """
2763  if shutil.which("valgrind") is None:
2764  self.skipTest("Missing valgrind")
2765 
2766  return_code, stdout, stderr = run_ns3("build scratch-simulator")
2767  self.assertEqual(return_code, 0)
2768 
2769  return_code, stdout, stderr = run_ns3(
2770  "run scratch-simulator --valgrind --verbose --no-build"
2771  )
2772  self.assertEqual(return_code, 0)
2773  self.assertIn("scratch-simulator", stderr)
2774  self.assertIn("Memcheck", stderr)
2775 
2777  """!
2778  Test the doxygen target that does trigger a full build
2779  @return None
2780  """
2781  if shutil.which("doxygen") is None:
2782  self.skipTest("Missing doxygen")
2783 
2784  if shutil.which("bash") is None:
2785  self.skipTest("Missing bash")
2786 
2787  doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
2788 
2789  doxygen_files = ["introspected-command-line.h", "introspected-doxygen.h"]
2790  for filename in doxygen_files:
2791  file_path = os.sep.join([doc_folder, filename])
2792  if os.path.exists(file_path):
2793  os.remove(file_path)
2794 
2795  # Rebuilding dot images is super slow, so not removing doxygen products
2796  # doxygen_build_folder = os.sep.join([doc_folder, "html"])
2797  # if os.path.exists(doxygen_build_folder):
2798  # shutil.rmtree(doxygen_build_folder)
2799 
2800  return_code, stdout, stderr = run_ns3("docs doxygen")
2801  self.assertEqual(return_code, 0)
2802  self.assertIn(cmake_build_target_command(target="doxygen"), stdout)
2803  self.assertIn("Built target doxygen", stdout)
2804 
2806  """!
2807  Test the doxygen target that doesn't trigger a full build
2808  @return None
2809  """
2810  if shutil.which("doxygen") is None:
2811  self.skipTest("Missing doxygen")
2812 
2813  # Rebuilding dot images is super slow, so not removing doxygen products
2814  # doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
2815  # doxygen_build_folder = os.sep.join([doc_folder, "html"])
2816  # if os.path.exists(doxygen_build_folder):
2817  # shutil.rmtree(doxygen_build_folder)
2818 
2819  return_code, stdout, stderr = run_ns3("docs doxygen-no-build")
2820  self.assertEqual(return_code, 0)
2821  self.assertIn(cmake_build_target_command(target="doxygen-no-build"), stdout)
2822  self.assertIn("Built target doxygen-no-build", stdout)
2823 
2825  """!
2826  Test every individual target for Sphinx-based documentation
2827  @return None
2828  """
2829  if shutil.which("sphinx-build") is None:
2830  self.skipTest("Missing sphinx")
2831 
2832  doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
2833 
2834  # For each sphinx doc target.
2835  for target in ["installation", "contributing", "manual", "models", "tutorial"]:
2836  # First we need to clean old docs, or it will not make any sense.
2837  doc_build_folder = os.sep.join([doc_folder, target, "build"])
2838  doc_temp_folder = os.sep.join([doc_folder, target, "source-temp"])
2839  if os.path.exists(doc_build_folder):
2840  shutil.rmtree(doc_build_folder)
2841  if os.path.exists(doc_temp_folder):
2842  shutil.rmtree(doc_temp_folder)
2843 
2844  # Build
2845  return_code, stdout, stderr = run_ns3("docs %s" % target)
2846  self.assertEqual(return_code, 0, target)
2847  self.assertIn(cmake_build_target_command(target="sphinx_%s" % target), stdout)
2848  self.assertIn("Built target sphinx_%s" % target, stdout)
2849 
2850  # Check if the docs output folder exists
2851  doc_build_folder = os.sep.join([doc_folder, target, "build"])
2852  self.assertTrue(os.path.exists(doc_build_folder))
2853 
2854  # Check if the all the different types are in place (latex, split HTML and single page HTML)
2855  for build_type in ["latex", "html", "singlehtml"]:
2856  self.assertTrue(os.path.exists(os.sep.join([doc_build_folder, build_type])))
2857 
2859  """!
2860  Test the documentation target that builds
2861  both doxygen and sphinx based documentation
2862  @return None
2863  """
2864  if shutil.which("doxygen") is None:
2865  self.skipTest("Missing doxygen")
2866  if shutil.which("sphinx-build") is None:
2867  self.skipTest("Missing sphinx")
2868 
2869  doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
2870 
2871  # First we need to clean old docs, or it will not make any sense.
2872 
2873  # Rebuilding dot images is super slow, so not removing doxygen products
2874  # doxygen_build_folder = os.sep.join([doc_folder, "html"])
2875  # if os.path.exists(doxygen_build_folder):
2876  # shutil.rmtree(doxygen_build_folder)
2877 
2878  for target in ["manual", "models", "tutorial"]:
2879  doc_build_folder = os.sep.join([doc_folder, target, "build"])
2880  if os.path.exists(doc_build_folder):
2881  shutil.rmtree(doc_build_folder)
2882 
2883  return_code, stdout, stderr = run_ns3("docs all")
2884  self.assertEqual(return_code, 0)
2885  self.assertIn(cmake_build_target_command(target="sphinx"), stdout)
2886  self.assertIn("Built target sphinx", stdout)
2887  self.assertIn(cmake_build_target_command(target="doxygen"), stdout)
2888  self.assertIn("Built target doxygen", stdout)
2889 
2891  """!
2892  Try to set ownership of scratch-simulator from current user to root,
2893  and change execution permissions
2894  @return None
2895  """
2896 
2897  # Test will be skipped if not defined
2898  sudo_password = os.getenv("SUDO_PASSWORD", None)
2899 
2900  # Skip test if variable containing sudo password is the default value
2901  if sudo_password is None:
2902  self.skipTest("SUDO_PASSWORD environment variable was not specified")
2903 
2904  enable_sudo = read_lock_entry("ENABLE_SUDO")
2905  self.assertFalse(enable_sudo is True)
2906 
2907  # First we run to ensure the program was built
2908  return_code, stdout, stderr = run_ns3("run scratch-simulator")
2909  self.assertEqual(return_code, 0)
2910  self.assertIn("Built target scratch_scratch-simulator", stdout)
2911  self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout)
2912  scratch_simulator_path = list(
2913  filter(lambda x: x if "scratch-simulator" in x else None, self.ns3_executablesns3_executablesns3_executables)
2914  )[-1]
2915  prev_fstat = os.stat(scratch_simulator_path) # we get the permissions before enabling sudo
2916 
2917  # Now try setting the sudo bits from the run subparser
2918  return_code, stdout, stderr = run_ns3(
2919  "run scratch-simulator --enable-sudo", env={"SUDO_PASSWORD": sudo_password}
2920  )
2921  self.assertEqual(return_code, 0)
2922  self.assertIn("Built target scratch_scratch-simulator", stdout)
2923  self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout)
2924  fstat = os.stat(scratch_simulator_path)
2925 
2926  import stat
2927 
2928  # If we are on Windows, these permissions mean absolutely nothing,
2929  # and on Fuse builds they might not make any sense, so we need to skip before failing
2930  likely_fuse_mount = (
2931  (prev_fstat.st_mode & stat.S_ISUID) == (fstat.st_mode & stat.S_ISUID)
2932  ) and prev_fstat.st_uid == 0 # noqa
2933 
2934  if win32 or likely_fuse_mount:
2935  self.skipTest("Windows or likely a FUSE mount")
2936 
2937  # If this is a valid platform, we can continue
2938  self.assertEqual(fstat.st_uid, 0) # check the file was correctly chown'ed by root
2939  self.assertEqual(
2940  fstat.st_mode & stat.S_ISUID, stat.S_ISUID
2941  ) # check if normal users can run as sudo
2942 
2943  # Now try setting the sudo bits as a post-build step (as set by configure subparser)
2944  return_code, stdout, stderr = run_ns3("configure --enable-sudo")
2945  self.assertEqual(return_code, 0)
2946 
2947  # Check if it was properly set in the lock file
2948  enable_sudo = read_lock_entry("ENABLE_SUDO")
2949  self.assertTrue(enable_sudo)
2950 
2951  # Remove old executables
2952  for executable in self.ns3_executablesns3_executablesns3_executables:
2953  if os.path.exists(executable):
2954  os.remove(executable)
2955 
2956  # Try to build and then set sudo bits as a post-build step
2957  return_code, stdout, stderr = run_ns3("build", env={"SUDO_PASSWORD": sudo_password})
2958  self.assertEqual(return_code, 0)
2959 
2960  # Check if commands are being printed for every target
2961  self.assertIn("chown root", stdout)
2962  self.assertIn("chmod u+s", stdout)
2963  for executable in self.ns3_executablesns3_executablesns3_executables:
2964  self.assertIn(os.path.basename(executable), stdout)
2965 
2966  # Check scratch simulator yet again
2967  fstat = os.stat(scratch_simulator_path)
2968  self.assertEqual(fstat.st_uid, 0) # check the file was correctly chown'ed by root
2969  self.assertEqual(
2970  fstat.st_mode & stat.S_ISUID, stat.S_ISUID
2971  ) # check if normal users can run as sudo
2972 
2974  """!
2975  Check if command template is working
2976  @return None
2977  """
2978 
2979  # Command templates that are empty or do not have a '%s' should fail
2980  return_code0, stdout0, stderr0 = run_ns3("run sample-simulator --command-template")
2981  self.assertEqual(return_code0, 2)
2982  self.assertIn("argument --command-template: expected one argument", stderr0)
2983 
2984  return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template=" "')
2985  return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --command-template " "')
2986  return_code3, stdout3, stderr3 = run_ns3('run sample-simulator --command-template "echo "')
2987  self.assertEqual((return_code1, return_code2, return_code3), (1, 1, 1))
2988  for stderr in [stderr1, stderr2, stderr3]:
2989  self.assertIn("not all arguments converted during string formatting", stderr)
2990 
2991  # Command templates with %s should at least continue and try to run the target
2992  return_code4, stdout4, _ = run_ns3(
2993  'run sample-simulator --command-template "%s --PrintVersion" --verbose'
2994  )
2995  return_code5, stdout5, _ = run_ns3(
2996  'run sample-simulator --command-template="%s --PrintVersion" --verbose'
2997  )
2998  self.assertEqual((return_code4, return_code5), (0, 0))
2999 
3000  self.assertIn("sample-simulator{ext} --PrintVersion".format(ext=ext), stdout4)
3001  self.assertIn("sample-simulator{ext} --PrintVersion".format(ext=ext), stdout5)
3002 
3004  """!
3005  Check if all flavors of different argument passing to
3006  executable targets are working
3007  @return None
3008  """
3009 
3010  # Test if all argument passing flavors are working
3011  return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help" --verbose')
3012  return_code1, stdout1, stderr1 = run_ns3(
3013  'run sample-simulator --command-template="%s --help" --verbose'
3014  )
3015  return_code2, stdout2, stderr2 = run_ns3("run sample-simulator --verbose -- --help")
3016 
3017  self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
3018  self.assertIn("sample-simulator{ext} --help".format(ext=ext), stdout0)
3019  self.assertIn("sample-simulator{ext} --help".format(ext=ext), stdout1)
3020  self.assertIn("sample-simulator{ext} --help".format(ext=ext), stdout2)
3021 
3022  # Test if the same thing happens with an additional run argument (e.g. --no-build)
3023  return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help" --no-build')
3024  return_code1, stdout1, stderr1 = run_ns3(
3025  'run sample-simulator --command-template="%s --help" --no-build'
3026  )
3027  return_code2, stdout2, stderr2 = run_ns3("run sample-simulator --no-build -- --help")
3028  self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
3029  self.assertEqual(stdout0, stdout1)
3030  self.assertEqual(stdout1, stdout2)
3031  self.assertEqual(stderr0, stderr1)
3032  self.assertEqual(stderr1, stderr2)
3033 
3034  # Now collect results for each argument individually
3035  return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --PrintGlobals" --verbose')
3036  return_code1, stdout1, stderr1 = run_ns3('run "sample-simulator --PrintGroups" --verbose')
3037  return_code2, stdout2, stderr2 = run_ns3('run "sample-simulator --PrintTypeIds" --verbose')
3038 
3039  self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
3040  self.assertIn("sample-simulator{ext} --PrintGlobals".format(ext=ext), stdout0)
3041  self.assertIn("sample-simulator{ext} --PrintGroups".format(ext=ext), stdout1)
3042  self.assertIn("sample-simulator{ext} --PrintTypeIds".format(ext=ext), stdout2)
3043 
3044  # Then check if all the arguments are correctly merged by checking the outputs
3045  cmd = 'run "sample-simulator --PrintGlobals" --command-template="%s --PrintGroups" --verbose -- --PrintTypeIds'
3046  return_code, stdout, stderr = run_ns3(cmd)
3047  self.assertEqual(return_code, 0)
3048 
3049  # The order of the arguments is command template,
3050  # arguments passed with the target itself
3051  # and forwarded arguments after the -- separator
3052  self.assertIn(
3053  "sample-simulator{ext} --PrintGroups --PrintGlobals --PrintTypeIds".format(ext=ext),
3054  stdout,
3055  )
3056 
3057  # Check if it complains about the missing -- separator
3058  cmd0 = 'run sample-simulator --command-template="%s " --PrintTypeIds'
3059  cmd1 = "run sample-simulator --PrintTypeIds"
3060 
3061  return_code0, stdout0, stderr0 = run_ns3(cmd0)
3062  return_code1, stdout1, stderr1 = run_ns3(cmd1)
3063  self.assertEqual((return_code0, return_code1), (1, 1))
3064  self.assertIn("To forward configuration or runtime options, put them after '--'", stderr0)
3065  self.assertIn("To forward configuration or runtime options, put them after '--'", stderr1)
3066 
3068  """!
3069  Test if scratch simulator is executed through lldb
3070  @return None
3071  """
3072  if shutil.which("lldb") is None:
3073  self.skipTest("Missing lldb")
3074 
3075  return_code, stdout, stderr = run_ns3("build scratch-simulator")
3076  self.assertEqual(return_code, 0)
3077 
3078  return_code, stdout, stderr = run_ns3("run scratch-simulator --lldb --verbose --no-build")
3079  self.assertEqual(return_code, 0)
3080  self.assertIn("scratch-simulator", stdout)
3081  self.assertIn("(lldb) target create", stdout)
3082 
3084  """!
3085  Test if CPM and Vcpkg package managers are working properly
3086  @return None
3087  """
3088  # Clean the ns-3 configuration
3089  return_code, stdout, stderr = run_ns3("clean")
3090  self.assertEqual(return_code, 0)
3091 
3092  # Cleanup VcPkg leftovers
3093  if os.path.exists("vcpkg"):
3094  shutil.rmtree("vcpkg")
3095 
3096  # Copy a test module that consumes armadillo
3097  destination_src = os.path.join(ns3_path, "src/test-package-managers")
3098  # Remove pre-existing directories
3099  if os.path.exists(destination_src):
3100  shutil.rmtree(destination_src)
3101 
3102  # Always use a fresh copy
3103  shutil.copytree(
3104  os.path.join(ns3_path, "build-support/test-files/test-package-managers"),
3105  destination_src,
3106  )
3107 
3108  with DockerContainerManager(self, "ubuntu:22.04") as container:
3109  # Install toolchain
3110  container.execute("apt-get update")
3111  container.execute("apt-get install -y python3 cmake g++ ninja-build")
3112 
3113  # Verify that Armadillo is not available and that we did not
3114  # add any new unnecessary dependency when features are not used
3115  try:
3116  container.execute("./ns3 configure -- -DTEST_PACKAGE_MANAGER:STRING=ON")
3117  self.skipTest("Armadillo is already installed")
3118  except DockerException as e:
3119  pass
3120 
3121  # Clean cache to prevent dumb errors
3122  return_code, stdout, stderr = run_ns3("clean")
3123  self.assertEqual(return_code, 0)
3124 
3125  # Install CPM and VcPkg shared dependency
3126  container.execute("apt-get install -y git")
3127 
3128  # Install Armadillo with CPM
3129  try:
3130  container.execute(
3131  "./ns3 configure -- -DNS3_CPM=ON -DTEST_PACKAGE_MANAGER:STRING=CPM"
3132  )
3133  except DockerException as e:
3134  self.fail()
3135 
3136  # Try to build module using CPM's Armadillo
3137  try:
3138  container.execute("./ns3 build test-package-managers")
3139  except DockerException as e:
3140  self.fail()
3141 
3142  # Clean cache to prevent dumb errors
3143  return_code, stdout, stderr = run_ns3("clean")
3144  self.assertEqual(return_code, 0)
3145 
3146  # Install VcPkg dependencies
3147  container.execute("apt-get install -y zip unzip tar curl")
3148 
3149  # Install Armadillo dependencies
3150  container.execute("apt-get install -y pkg-config gfortran")
3151 
3152  # Install VcPkg
3153  try:
3154  container.execute("./ns3 configure -- -DNS3_VCPKG=ON")
3155  except DockerException as e:
3156  self.fail()
3157 
3158  # Install Armadillo with VcPkg
3159  try:
3160  container.execute("./ns3 configure -- -DTEST_PACKAGE_MANAGER:STRING=VCPKG")
3161  except DockerException as e:
3162  self.fail()
3163 
3164  # Try to build module using VcPkg's Armadillo
3165  try:
3166  container.execute("./ns3 build test-package-managers")
3167  except DockerException as e:
3168  self.fail()
3169 
3170  # Remove test module
3171  if os.path.exists(destination_src):
3172  shutil.rmtree(destination_src)
3173 
3174 
3175 class NS3QualityControlTestCase(unittest.TestCase):
3176  """!
3177  ns-3 tests to control the quality of the repository over time,
3178  by checking the state of URLs listed and more
3179  """
3180 
3182  """!
3183  Test if all urls in source files are alive
3184  @return None
3185  """
3186 
3187  # Skip this test if Django is not available
3188  try:
3189  import django
3190  except ImportError:
3191  django = None # noqa
3192  self.skipTest("Django URL validators are not available")
3193 
3194  # Skip this test if requests library is not available
3195  try:
3196  import requests
3197  import urllib3
3198 
3199  urllib3.disable_warnings()
3200  except ImportError:
3201  requests = None # noqa
3202  self.skipTest("Requests library is not available")
3203 
3204  regex = re.compile(r"((http|https)://[^\ \n\‍)\"\'\}><\]\;\`\\]*)") # noqa
3205  skipped_files = []
3206 
3207  whitelisted_urls = {
3208  "https://gitlab.com/your-user-name/ns-3-dev",
3209  "https://www.nsnam.org/release/ns-allinone-3.31.rc1.tar.bz2",
3210  "https://www.nsnam.org/release/ns-allinone-3.X.rcX.tar.bz2",
3211  "https://www.nsnam.org/releases/ns-3-x",
3212  "https://www.nsnam.org/releases/ns-allinone-3.(x-1",
3213  "https://www.nsnam.org/releases/ns-allinone-3.x.tar.bz2",
3214  "https://ns-buildmaster.ee.washington.edu:8010/",
3215  # split due to command-line formatting
3216  "https://cmake.org/cmake/help/latest/manual/cmake-",
3217  "http://www.ieeeghn.org/wiki/index.php/First-Hand:Digital_Television:_The_",
3218  # Dia placeholder xmlns address
3219  "http://www.lysator.liu.se/~alla/dia/",
3220  # Fails due to bad regex
3221  "http://www.ieeeghn.org/wiki/index.php/First-Hand:Digital_Television:_The_Digital_Terrestrial_Television_Broadcasting_(DTTB",
3222  "http://en.wikipedia.org/wiki/Namespace_(computer_science",
3223  "http://en.wikipedia.org/wiki/Bonobo_(component_model",
3224  "http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85",
3225  # historical links
3226  "http://www.research.att.com/info/kpv/",
3227  "http://www.research.att.com/~gsf/",
3228  "http://nsnam.isi.edu/nsnam/index.php/Contributed_Code",
3229  "http://scan5.coverity.com/cgi-bin/upload.py",
3230  # terminal output
3231  "https://github.com/Kitware/CMake/releases/download/v3.27.1/cmake-3.27.1-linux-x86_64.tar.gz-",
3232  "http://mirrors.kernel.org/fedora/releases/11/Everything/i386/os/Packages/",
3233  }
3234 
3235  # Scan for all URLs in all files we can parse
3236  files_and_urls = set()
3237  unique_urls = set()
3238  for topdir in ["bindings", "doc", "examples", "src", "utils"]:
3239  for root, dirs, files in os.walk(topdir):
3240  # do not parse files in build directories
3241  if "build" in root or "_static" in root or "source-temp" in root or "html" in root:
3242  continue
3243  for file in files:
3244  filepath = os.path.join(root, file)
3245 
3246  # skip everything that isn't a file
3247  if not os.path.isfile(filepath):
3248  continue
3249 
3250  # skip svg files
3251  if file.endswith(".svg"):
3252  continue
3253 
3254  try:
3255  with open(filepath, "r", encoding="utf-8") as f:
3256  matches = regex.findall(f.read())
3257 
3258  # Get first group for each match (containing the URL)
3259  # and strip final punctuation or commas in matched links
3260  # commonly found in the docs
3261  urls = list(
3262  map(lambda x: x[0][:-1] if x[0][-1] in ".," else x[0], matches)
3263  )
3264  except UnicodeDecodeError:
3265  skipped_files.append(filepath)
3266  continue
3267 
3268  # Search for new unique URLs and add keep track of their associated source file
3269  for url in set(urls) - unique_urls - whitelisted_urls:
3270  unique_urls.add(url)
3271  files_and_urls.add((filepath, url))
3272 
3273  # Instantiate the Django URL validator
3274  from django.core.exceptions import ValidationError # noqa
3275  from django.core.validators import URLValidator # noqa
3276 
3277  validate_url = URLValidator()
3278 
3279  # User agent string to make ACM and Elsevier let us check if links to papers are working
3280  headers = {
3281  "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36"
3282  # noqa
3283  }
3284 
3285  def test_file_url(args):
3286  test_filepath, test_url = args
3287  dead_link_msg = None
3288 
3289  # Skip invalid URLs
3290  try:
3291  validate_url(test_url)
3292  except ValidationError:
3293  dead_link_msg = "%s: URL %s, invalid URL" % (test_filepath, test_url)
3294  except Exception as e:
3295  self.assertEqual(False, True, msg=e.__str__())
3296 
3297  if dead_link_msg is not None:
3298  return dead_link_msg
3299  tries = 3
3300  # Check if valid URLs are alive
3301  while tries > 0:
3302  # Not verifying the certificate (verify=False) is potentially dangerous
3303  # HEAD checks are not as reliable as GET ones,
3304  # in some cases they may return bogus error codes and reasons
3305  try:
3306  response = requests.get(test_url, verify=False, headers=headers, timeout=50)
3307 
3308  # In case of success and redirection
3309  if response.status_code in [200, 301]:
3310  dead_link_msg = None
3311  break
3312 
3313  # People use the wrong code, but the reason
3314  # can still be correct
3315  if response.status_code in [302, 308, 500, 503]:
3316  if response.reason.lower() in [
3317  "found",
3318  "moved temporarily",
3319  "permanent redirect",
3320  "ok",
3321  "service temporarily unavailable",
3322  ]:
3323  dead_link_msg = None
3324  break
3325  # In case it didn't pass in any of the previous tests,
3326  # set dead_link_msg with the most recent error and try again
3327  dead_link_msg = "%s: URL %s: returned code %d" % (
3328  test_filepath,
3329  test_url,
3330  response.status_code,
3331  )
3332  except requests.exceptions.InvalidURL:
3333  dead_link_msg = "%s: URL %s: invalid URL" % (test_filepath, test_url)
3334  except requests.exceptions.SSLError:
3335  dead_link_msg = "%s: URL %s: SSL error" % (test_filepath, test_url)
3336  except requests.exceptions.TooManyRedirects:
3337  dead_link_msg = "%s: URL %s: too many redirects" % (test_filepath, test_url)
3338  except Exception as e:
3339  try:
3340  error_msg = e.args[0].reason.__str__()
3341  except AttributeError:
3342  error_msg = e.args[0]
3343  dead_link_msg = "%s: URL %s: failed with exception: %s" % (
3344  test_filepath,
3345  test_url,
3346  error_msg,
3347  )
3348  tries -= 1
3349  return dead_link_msg
3350 
3351  # Dispatch threads to test multiple URLs concurrently
3352  from concurrent.futures import ThreadPoolExecutor
3353 
3354  with ThreadPoolExecutor(max_workers=100) as executor:
3355  dead_links = list(executor.map(test_file_url, list(files_and_urls)))
3356 
3357  # Filter out None entries
3358  dead_links = list(sorted(filter(lambda x: x is not None, dead_links)))
3359  self.assertEqual(len(dead_links), 0, msg="\n".join(["Dead links found:", *dead_links]))
3360 
3362  """!
3363  Test if all tests can be executed without hitting major memory bugs
3364  @return None
3365  """
3366  return_code, stdout, stderr = run_ns3(
3367  "configure --enable-tests --enable-examples --enable-sanitizers -d optimized"
3368  )
3369  self.assertEqual(return_code, 0)
3370 
3371  test_return_code, stdout, stderr = run_program("test.py", "", python=True)
3372  self.assertEqual(test_return_code, 0)
3373 
3375  """!
3376  Check if images in the docs are above a brightness threshold.
3377  This should prevent screenshots with dark UI themes.
3378  @return None
3379  """
3380  if shutil.which("convert") is None:
3381  self.skipTest("Imagemagick was not found")
3382 
3383  from pathlib import Path
3384 
3385  # Scan for images
3386  image_extensions = ["png", "jpg"]
3387  images = []
3388  for extension in image_extensions:
3389  images += list(Path("./doc").glob("**/figures/*.{ext}".format(ext=extension)))
3390  images += list(Path("./doc").glob("**/figures/**/*.{ext}".format(ext=extension)))
3391 
3392  # Get the brightness of an image on a scale of 0-100%
3393  imagemagick_get_image_brightness = 'convert {image} -colorspace HSI -channel b -separate +channel -scale 1x1 -format "%[fx:100*u]" info:'
3394 
3395  # We could invert colors of target image to increase its brightness
3396  # convert source.png -channel RGB -negate target.png
3397  brightness_threshold = 50
3398  for image in images:
3399  brightness = subprocess.check_output(
3400  imagemagick_get_image_brightness.format(image=image).split()
3401  )
3402  brightness = float(brightness.decode().strip("'\""))
3403  self.assertGreater(
3404  brightness,
3405  brightness_threshold,
3406  "Image darker than threshold (%d < %d): %s"
3407  % (brightness, brightness_threshold, image),
3408  )
3409 
3410 
3411 def main():
3412  """!
3413  Main function
3414  @return None
3415  """
3416 
3417  test_completeness = {
3418  "style": [
3419  NS3UnusedSourcesTestCase,
3420  NS3StyleTestCase,
3421  ],
3422  "build": [
3423  NS3CommonSettingsTestCase,
3424  NS3ConfigureBuildProfileTestCase,
3425  NS3ConfigureTestCase,
3426  NS3BuildBaseTestCase,
3427  NS3ExpectedUseTestCase,
3428  ],
3429  "complete": [
3430  NS3UnusedSourcesTestCase,
3431  NS3StyleTestCase,
3432  NS3CommonSettingsTestCase,
3433  NS3ConfigureBuildProfileTestCase,
3434  NS3ConfigureTestCase,
3435  NS3BuildBaseTestCase,
3436  NS3ExpectedUseTestCase,
3437  NS3QualityControlTestCase,
3438  ],
3439  "extras": [
3440  NS3DependenciesTestCase,
3441  ],
3442  }
3443 
3444  import argparse
3445 
3446  parser = argparse.ArgumentParser("Test suite for the ns-3 buildsystem")
3447  parser.add_argument(
3448  "-c", "--completeness", choices=test_completeness.keys(), default="complete"
3449  )
3450  parser.add_argument("-tn", "--test-name", action="store", default=None, type=str)
3451  parser.add_argument("-rtn", "--resume-from-test-name", action="store", default=None, type=str)
3452  parser.add_argument("-q", "--quiet", action="store_true", default=False)
3453  args = parser.parse_args(sys.argv[1:])
3454 
3455  loader = unittest.TestLoader()
3456  suite = unittest.TestSuite()
3457 
3458  # Put tests cases in order
3459  for testCase in test_completeness[args.completeness]:
3460  suite.addTests(loader.loadTestsFromTestCase(testCase))
3461 
3462  # Filter tests by name
3463  if args.test_name:
3464  # Generate a dictionary of test names and their objects
3465  tests = dict(map(lambda x: (x._testMethodName, x), suite._tests))
3466 
3467  tests_to_run = set(map(lambda x: x if args.test_name in x else None, tests.keys()))
3468  tests_to_remove = set(tests) - set(tests_to_run)
3469  for test_to_remove in tests_to_remove:
3470  suite._tests.remove(tests[test_to_remove])
3471 
3472  # Filter tests after a specific name (e.g. to restart from a failing test)
3473  if args.resume_from_test_name:
3474  # Generate a dictionary of test names and their objects
3475  tests = dict(map(lambda x: (x._testMethodName, x), suite._tests))
3476  keys = list(tests.keys())
3477 
3478  while args.resume_from_test_name not in keys[0] and len(tests) > 0:
3479  suite._tests.remove(tests[keys[0]])
3480  keys.pop(0)
3481 
3482  # Before running, check if ns3rc exists and save it
3483  ns3rc_script_bak = ns3rc_script + ".bak"
3484  if os.path.exists(ns3rc_script) and not os.path.exists(ns3rc_script_bak):
3485  shutil.move(ns3rc_script, ns3rc_script_bak)
3486 
3487  # Run tests and fail as fast as possible
3488  runner = unittest.TextTestRunner(failfast=True, verbosity=1 if args.quiet else 2)
3489  runner.run(suite)
3490 
3491  # After completing the tests successfully, restore the ns3rc file
3492  if os.path.exists(ns3rc_script_bak):
3493  shutil.move(ns3rc_script_bak, ns3rc_script)
3494 
3495 
3496 if __name__ == "__main__":
3497  main()
#define max(a, b)
Definition: 80211b.c:42
Python-on-whales wrapper for Docker-based ns-3 tests.
Definition: test-ns3.py:195
def __exit__(self, exc_type, exc_val, exc_tb)
Clean up the managed container at the end of the block "with DockerContainerManager() as container".
Definition: test-ns3.py:249
def __init__(self, unittest.TestCase currentTestCase, str containerName="ubuntu:latest")
Create and start container with containerName in the current ns-3 directory.
Definition: test-ns3.py:200
def __enter__(self)
Return the managed container when entiring the block "with DockerContainerManager() as container".
Definition: test-ns3.py:241
container
The Python-on-whales container instance.
Definition: test-ns3.py:226
Generic test case with basic function inherited by more complex tests.
Definition: test-ns3.py:790
def config_ok(self, return_code, stdout, stderr)
Check if configuration for release mode worked normally.
Definition: test-ns3.py:795
ns3_executables
ns3_executables holds a list of executables in .lock-ns3 # noqa
Definition: test-ns3.py:830
def setUp(self)
Clean configuration/build artifacts before testing configuration and build settings After configuring...
Definition: test-ns3.py:808
ns3_modules
ns3_modules holds a list to the modules enabled stored in .lock-ns3 # noqa
Definition: test-ns3.py:835
Tests ns3 regarding building the project.
Definition: test-ns3.py:2064
def test_06_TestVersionFile(self)
Test if changing the version file affects the library names.
Definition: test-ns3.py:2139
def test_10_AmbiguityCheck(self)
Test if ns3 can alert correctly in case a shortcut collision happens.
Definition: test-ns3.py:2461
def test_01_BuildExistingTargets(self)
Try building the core library.
Definition: test-ns3.py:2078
def test_12_CppyyBindings(self)
Test if we can use python bindings.
Definition: test-ns3.py:2542
def test_08_InstallationAndUninstallation(self)
Tries setting a ns3 version, then installing it.
Definition: test-ns3.py:2259
def test_11_StaticBuilds(self)
Test if we can build a static ns-3 library and link it to static programs.
Definition: test-ns3.py:2515
def setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned.
Definition: test-ns3.py:2069
def test_02_BuildNonExistingTargets(self)
Try building core-test library without tests enabled.
Definition: test-ns3.py:2087
def test_04_BuildProjectNoTaskLines(self)
Try hiding task lines.
Definition: test-ns3.py:2109
def test_14_LinkContribModuleToSrcModule(self)
Test if we can link contrib modules to src modules.
Definition: test-ns3.py:2592
def test_03_BuildProject(self)
Try building the project:
Definition: test-ns3.py:2097
def test_13_FetchOptionalComponents(self)
Test if we had regressions with brite, click and openflow modules that depend on homonymous libraries...
Definition: test-ns3.py:2574
def test_09_Scratches(self)
Tries to build scratch-simulator and subdir/scratch-simulator-subdir.
Definition: test-ns3.py:2433
def test_05_BreakBuild(self)
Try removing an essential file to break the build.
Definition: test-ns3.py:2118
ns3_executables
ns3_executables holds a list of executables in .lock-ns3 # noqa
Definition: test-ns3.py:2198
def test_07_OutputDirectory(self)
Try setting a different output directory and if everything is in the right place and still working co...
Definition: test-ns3.py:2183
ns3_libraries
ns3_libraries holds a list of built module libraries # noqa
Definition: test-ns3.py:2076
ns3 tests related to generic options
Definition: test-ns3.py:605
def test_05_CheckVersion(self)
Test only passing 'show version' argument to ns3.
Definition: test-ns3.py:655
def setUp(self)
Clean configuration/build artifacts before common commands.
Definition: test-ns3.py:610
def test_01_NoOption(self)
Test not passing any arguments to.
Definition: test-ns3.py:619
def test_02_NoTaskLines(self)
Test only passing –quiet argument to ns3.
Definition: test-ns3.py:628
def test_03_CheckConfig(self)
Test only passing 'show config' argument to ns3.
Definition: test-ns3.py:637
def test_04_CheckProfile(self)
Test only passing 'show profile' argument to ns3.
Definition: test-ns3.py:646
ns3 tests related to build profiles
Definition: test-ns3.py:665
def test_05_TYPO(self)
Test a build type with another typo.
Definition: test-ns3.py:740
def test_06_OverwriteDefaultSettings(self)
Replace settings set by default (e.g.
Definition: test-ns3.py:749
def test_02_Release(self)
Test the release build.
Definition: test-ns3.py:700
def test_01_Debug(self)
Test the debug build.
Definition: test-ns3.py:679
def setUp(self)
Clean configuration/build artifacts before testing configuration settings.
Definition: test-ns3.py:670
def test_03_Optimized(self)
Test the optimized build.
Definition: test-ns3.py:710
def test_04_Typo(self)
Test a build type with a typo.
Definition: test-ns3.py:731
Test ns3 configuration options.
Definition: test-ns3.py:838
def test_25_CheckBareConfig(self)
Check for regressions in a bare ns-3 configuration.
Definition: test-ns3.py:2042
def setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned.
Definition: test-ns3.py:843
def test_06_DisableModulesComma(self)
Test disabling comma-separated (waf-style) examples.
Definition: test-ns3.py:980
def test_04_DisableModules(self)
Test disabling specific modules.
Definition: test-ns3.py:932
def test_03_EnableModules(self)
Test enabling specific modules.
Definition: test-ns3.py:900
def test_24_CheckTestSettings(self)
Check for regressions in test object build.
Definition: test-ns3.py:2027
def test_18_CheckBuildVersionAndVersionCache(self)
Check if ENABLE_BUILD_VERSION and version.cache are working as expected.
Definition: test-ns3.py:1653
type
python-based ns3rc template # noqa
Definition: test-ns3.py:1038
def test_02_Tests(self)
Test enabling and disabling tests.
Definition: test-ns3.py:873
def test_19_FilterModuleExamplesAndTests(self)
Test filtering in examples and tests from specific modules.
Definition: test-ns3.py:1735
def test_09_PropagationOfReturnCode(self)
Test if ns3 is propagating back the return code from the executables called with the run command.
Definition: test-ns3.py:1217
def test_12_CheckVersion(self)
Test passing 'show version' argument to ns3 to get the build version.
Definition: test-ns3.py:1310
def test_05_EnableModulesComma(self)
Test enabling comma-separated (waf-style) examples.
Definition: test-ns3.py:956
def test_01_Examples(self)
Test enabling and disabling examples.
Definition: test-ns3.py:850
def test_14_MpiCommandTemplate(self)
Test if ns3 is inserting additional arguments by MPICH and OpenMPI to run on the CI.
Definition: test-ns3.py:1422
def test_21_ClangTimeTrace(self)
Check if NS3_CLANG_TIMETRACE feature is working Clang's -ftime-trace plus ClangAnalyzer report.
Definition: test-ns3.py:1850
def test_23_PrecompiledHeaders(self)
Check if precompiled headers are being enabled correctly.
Definition: test-ns3.py:1996
def test_16_LibrariesContainingLib(self)
Test if CMake can properly handle modules containing "lib", which is used internally as a prefix for ...
Definition: test-ns3.py:1586
def test_17_CMakePerformanceTracing(self)
Test if CMake performance tracing works and produces the cmake_performance_trace.log file.
Definition: test-ns3.py:1632
def test_07_Ns3rc(self)
Test loading settings from the ns3rc config file.
Definition: test-ns3.py:1004
def test_13_Scratches(self)
Test if CMake target names for scratches and ns3 shortcuts are working correctly.
Definition: test-ns3.py:1325
def test_08_DryRun(self)
Test dry-run (printing commands to be executed instead of running them)
Definition: test-ns3.py:1161
def test_10_CheckConfig(self)
Test passing 'show config' argument to ns3 to get the configuration table.
Definition: test-ns3.py:1292
def test_15_InvalidLibrariesToLink(self)
Test if CMake and ns3 fail in the expected ways when:
Definition: test-ns3.py:1479
def test_22_NinjaTrace(self)
Check if NS3_NINJA_TRACE feature is working Ninja's .ninja_log conversion to about://tracing json for...
Definition: test-ns3.py:1909
def test_20_CheckFastLinkers(self)
Check if fast linkers LLD and Mold are correctly found and configured.
Definition: test-ns3.py:1783
def test_11_CheckProfile(self)
Test passing 'show profile' argument to ns3 to get the build profile.
Definition: test-ns3.py:1301
ns-3 tests related to dependencies
Definition: test-ns3.py:385
def test_01_CheckIfIncludedHeadersMatchLinkedModules(self)
Checks if headers from different modules (src/A, contrib/B) that are included by the current module (...
Definition: test-ns3.py:390
Tests ns3 usage in more realistic scenarios.
Definition: test-ns3.py:2631
def test_10_DoxygenWithBuild(self)
Test the doxygen target that does trigger a full build.
Definition: test-ns3.py:2776
def test_02_BuildAndRunExistingExecutableTarget(self)
Try to build and run test-runner.
Definition: test-ns3.py:2678
def test_08_RunNoBuildGdb(self)
Test if scratch simulator is executed through gdb and lldb.
Definition: test-ns3.py:2737
def test_05_RunNoBuildExistingExecutableTarget(self)
Try to run test-runner without building.
Definition: test-ns3.py:2706
def test_06_RunNoBuildExistingLibraryTarget(self)
Test ns3 fails to run a library.
Definition: test-ns3.py:2719
def test_03_BuildAndRunExistingLibraryTarget(self)
Try to build and run a library.
Definition: test-ns3.py:2688
def test_01_BuildProject(self)
Try to build the project.
Definition: test-ns3.py:2663
ns3_modules
ns3_modules holds a list to the modules enabled stored in .lock-ns3 # noqa
Definition: test-ns3.py:2661
def test_14_EnableSudo(self)
Try to set ownership of scratch-simulator from current user to root, and change execution permissions...
Definition: test-ns3.py:2890
def test_16_ForwardArgumentsToRunTargets(self)
Check if all flavors of different argument passing to executable targets are working.
Definition: test-ns3.py:3003
def test_17_RunNoBuildLldb(self)
Test if scratch simulator is executed through lldb.
Definition: test-ns3.py:3067
def test_15_CommandTemplate(self)
Check if command template is working.
Definition: test-ns3.py:2973
def test_04_BuildAndRunNonExistingTarget(self)
Try to build and run an unknown target.
Definition: test-ns3.py:2697
def test_07_RunNoBuildNonExistingExecutableTarget(self)
Test ns3 fails to run an unknown program.
Definition: test-ns3.py:2728
def test_18_CpmAndVcpkgManagers(self)
Test if CPM and Vcpkg package managers are working properly.
Definition: test-ns3.py:3083
ns3_executables
ns3_executables holds a list of executables in .lock-ns3 # noqa
Definition: test-ns3.py:2655
def test_09_RunNoBuildValgrind(self)
Test if scratch simulator is executed through valgrind.
Definition: test-ns3.py:2758
def test_13_Documentation(self)
Test the documentation target that builds both doxygen and sphinx based documentation.
Definition: test-ns3.py:2858
def setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned Here examples,...
Definition: test-ns3.py:2636
def test_11_DoxygenWithoutBuild(self)
Test the doxygen target that doesn't trigger a full build.
Definition: test-ns3.py:2805
def test_12_SphinxDocumentation(self)
Test every individual target for Sphinx-based documentation.
Definition: test-ns3.py:2824
ns-3 tests to control the quality of the repository over time, by checking the state of URLs listed a...
Definition: test-ns3.py:3175
def test_03_CheckImageBrightness(self)
Check if images in the docs are above a brightness threshold.
Definition: test-ns3.py:3374
def test_02_MemoryCheckWithSanitizers(self)
Test if all tests can be executed without hitting major memory bugs.
Definition: test-ns3.py:3361
def test_01_CheckForDeadLinksInSources(self)
Test if all urls in source files are alive.
Definition: test-ns3.py:3181
ns-3 tests to check if the source code, whitespaces and CMake formatting are according to the coding ...
Definition: test-ns3.py:539
def test_01_CheckCMakeFormat(self)
Check if there is any difference between tracked file after applying cmake-format.
Definition: test-ns3.py:577
None setUp(self)
Import GitRepo and load the original diff state of the repository before the tests.
Definition: test-ns3.py:550
ns-3 tests related to checking if source files were left behind, not being used by CMake
Definition: test-ns3.py:262
dictionary directory_and_files
dictionary containing directories with .cc source files # noqa
Definition: test-ns3.py:268
def test_01_UnusedExampleSources(self)
Test if all example source files are being used in their respective CMakeLists.txt.
Definition: test-ns3.py:286
def setUp(self)
Scan all C++ source files and add them to a list based on their path.
Definition: test-ns3.py:270
def test_02_UnusedModuleSources(self)
Test if all module source files are being used in their respective CMakeLists.txt.
Definition: test-ns3.py:311
def test_03_UnusedUtilsSources(self)
Test if all utils source files are being used in their respective CMakeLists.txt.
Definition: test-ns3.py:354
def run_ns3(args, env=None, generator=platform_makefiles)
Runs the ns3 wrapper script with arguments.
Definition: test-ns3.py:59
def get_programs_list()
Extracts the programs list from .lock-ns3.
Definition: test-ns3.py:131
def get_libraries_list(lib_outdir=usual_lib_outdir)
Gets a list of built libraries.
Definition: test-ns3.py:148
def get_test_enabled()
Check if tests are enabled in the .lock-ns3.
Definition: test-ns3.py:179
def read_lock_entry(entry)
Read interesting entries from the .lock-ns3 file.
Definition: test-ns3.py:167
cmake_build_target_command
Definition: test-ns3.py:49
def get_headers_list(outdir=usual_outdir)
Gets a list of header files.
Definition: test-ns3.py:158
def run_program(program, args, python=False, cwd=ns3_path, env=None)
Runs a program with the given arguments and returns a tuple containing (error code,...
Definition: test-ns3.py:82
def get_enabled_modules()
Definition: test-ns3.py:187
#define list