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