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 cmake_build_project_command = "cmake --build . -j".format(ns3_path=ns3_path)
47 cmake_build_target_command = partial("cmake --build . -j {jobs} --target {target}".format,
48  jobs=max(1, os.cpu_count() - 1)
49  )
50 
51 
52 def run_ns3(args, env=None):
53  """!
54  Runs the ns3 wrapper script with arguments
55  @param args: string containing arguments that will get split before calling ns3
56  @param env: environment variables dictionary
57  @return tuple containing (error code, stdout and stderr)
58  """
59  if "clean" in args:
60  possible_leftovers = ["contrib/borked", "contrib/calibre"]
61  for leftover in possible_leftovers:
62  if os.path.exists(leftover):
63  shutil.rmtree(leftover, ignore_errors=True)
64  return run_program(ns3_script, args, python=True, env=env)
65 
66 
67 # Adapted from https://github.com/metabrainz/picard/blob/master/picard/util/__init__.py
68 def run_program(program, args, python=False, cwd=ns3_path, env=None):
69  """!
70  Runs a program with the given arguments and returns a tuple containing (error code, stdout and stderr)
71  @param program: program to execute (or python script)
72  @param args: string containing arguments that will get split before calling the program
73  @param python: flag indicating whether the program is a python script
74  @param cwd: the working directory used that will be the root folder for the execution
75  @param env: environment variables dictionary
76  @return tuple containing (error code, stdout and stderr)
77  """
78  if type(args) != str:
79  raise Exception("args should be a string")
80 
81  # Include python interpreter if running a python script
82  if python:
83  arguments = [sys.executable, program]
84  else:
85  arguments = [program]
86 
87  if args != "":
88  arguments.extend(re.findall("(?:\".*?\"|\S)+", args))
89 
90  for i in range(len(arguments)):
91  arguments[i] = arguments[i].replace("\"", "")
92 
93  # Forward environment variables used by the ns3 script
94  current_env = os.environ.copy()
95 
96  # Add different environment variables
97  if env:
98  current_env.update(env)
99 
100  # Call program with arguments
101  ret = subprocess.run(
102  arguments,
103  stdin=subprocess.DEVNULL,
104  stdout=subprocess.PIPE,
105  stderr=subprocess.PIPE,
106  cwd=cwd, # run process from the ns-3-dev path
107  env=current_env
108  )
109  # Return (error code, stdout and stderr)
110  return ret.returncode, ret.stdout.decode(sys.stdout.encoding), ret.stderr.decode(sys.stderr.encoding)
111 
112 
114  """!
115  Extracts the programs list from .lock-ns3
116  @return list of programs.
117  """
118  values = {}
119  with open(ns3_lock_filename) as f:
120  exec(f.read(), globals(), values)
121  return values["ns3_runnable_programs"]
122 
123 
124 def get_libraries_list(lib_outdir=usual_lib_outdir):
125  """!
126  Gets a list of built libraries
127  @param lib_outdir: path containing libraries
128  @return list of built libraries.
129  """
130  return glob.glob(lib_outdir + '/*', recursive=True)
131 
132 
133 def get_headers_list(outdir=usual_outdir):
134  """!
135  Gets a list of header files
136  @param outdir: path containing headers
137  @return list of headers.
138  """
139  return glob.glob(outdir + '/**/*.h', recursive=True)
140 
141 
142 def read_lock_entry(entry):
143  """!
144  Read interesting entries from the .lock-ns3 file
145  @param entry: entry to read from .lock-ns3
146  @return value of the requested entry.
147  """
148  values = {}
149  with open(ns3_lock_filename) as f:
150  exec(f.read(), globals(), values)
151  return values.get(entry, None)
152 
153 
155  """!
156  Check if tests are enabled in the .lock-ns3
157  @return bool.
158  """
159  return read_lock_entry("ENABLE_TESTS")
160 
161 
163  """
164  Check if tests are enabled in the .lock-ns3
165  @return list of enabled modules (prefixed with 'ns3-').
166  """
167  return read_lock_entry("NS3_ENABLED_MODULES")
168 
169 
170 class NS3UnusedSourcesTestCase(unittest.TestCase):
171  """!
172  ns-3 tests related to checking if source files were left behind, not being used by CMake
173  """
174 
175 
176  directory_and_files = {}
177 
178  def setUp(self):
179  """!
180  Scan all C++ source files and add them to a list based on their path
181  @return None
182  """
183  for root, dirs, files in os.walk(ns3_path):
184  if "gitlab-ci-local" in root:
185  continue
186  for name in files:
187  if name.endswith(".cc"):
188  path = os.path.join(root, name)
189  directory = os.path.dirname(path)
190  if directory not in self.directory_and_filesdirectory_and_files:
191  self.directory_and_filesdirectory_and_files[directory] = []
192  self.directory_and_filesdirectory_and_files[directory].append(path)
193 
195  """!
196  Test if all example source files are being used in their respective CMakeLists.txt
197  @return None
198  """
199  unused_sources = set()
200  for example_directory in self.directory_and_filesdirectory_and_files.keys():
201  # Skip non-example directories
202  if os.sep + "examples" not in example_directory:
203  continue
204 
205  # Open the examples CMakeLists.txt and read it
206  with open(os.path.join(example_directory, "CMakeLists.txt"), "r") as f:
207  cmake_contents = f.read()
208 
209  # For each file, check if it is in the CMake contents
210  for file in self.directory_and_filesdirectory_and_files[example_directory]:
211  # We remove the .cc because some examples sources can be written as ${example_name}.cc
212  if os.path.basename(file).replace(".cc", "") not in cmake_contents:
213  unused_sources.add(file)
214 
215  self.assertListEqual([], list(unused_sources))
216 
218  """!
219  Test if all module source files are being used in their respective CMakeLists.txt
220  @return None
221  """
222  unused_sources = set()
223  for directory in self.directory_and_filesdirectory_and_files.keys():
224  # Skip examples and bindings directories
225  is_not_module = not ("src" in directory or "contrib" in directory)
226  is_example = os.sep + "examples" in directory
227  is_bindings = os.sep + "bindings" in directory
228 
229  if is_not_module or is_bindings or is_example:
230  continue
231 
232  # We can be in one of the module subdirectories (helper, model, test, bindings, etc)
233  # Navigate upwards until we hit a CMakeLists.txt
234  cmake_path = os.path.join(directory, "CMakeLists.txt")
235  while not os.path.exists(cmake_path):
236  parent_directory = os.path.dirname(os.path.dirname(cmake_path))
237  cmake_path = os.path.join(parent_directory, os.path.basename(cmake_path))
238 
239  # Open the module CMakeLists.txt and read it
240  with open(cmake_path, "r") as f:
241  cmake_contents = f.read()
242 
243  # For each file, check if it is in the CMake contents
244  for file in self.directory_and_filesdirectory_and_files[directory]:
245  if os.path.basename(file) not in cmake_contents:
246  unused_sources.add(file)
247 
248  # Remove temporary exceptions
249  exceptions = ["win32-system-wall-clock-ms.cc", # Should be removed with MR784
250  ]
251  for exception in exceptions:
252  for unused_source in unused_sources:
253  if os.path.basename(unused_source) == exception:
254  unused_sources.remove(unused_source)
255  break
256 
257  self.assertListEqual([], list(unused_sources))
258 
260  """!
261  Test if all utils source files are being used in their respective CMakeLists.txt
262  @return None
263  """
264  unused_sources = set()
265  for directory in self.directory_and_filesdirectory_and_files.keys():
266  # Skip directories that are not utils
267  is_module = "src" in directory or "contrib" in directory
268  if os.sep + "utils" not in directory or is_module:
269  continue
270 
271  # We can be in one of the module subdirectories (helper, model, test, bindings, etc)
272  # Navigate upwards until we hit a CMakeLists.txt
273  cmake_path = os.path.join(directory, "CMakeLists.txt")
274  while not os.path.exists(cmake_path):
275  parent_directory = os.path.dirname(os.path.dirname(cmake_path))
276  cmake_path = os.path.join(parent_directory, os.path.basename(cmake_path))
277 
278  # Open the module CMakeLists.txt and read it
279  with open(cmake_path, "r") as f:
280  cmake_contents = f.read()
281 
282  # For each file, check if it is in the CMake contents
283  for file in self.directory_and_filesdirectory_and_files[directory]:
284  if os.path.basename(file) not in cmake_contents:
285  unused_sources.add(file)
286 
287  self.assertListEqual([], list(unused_sources))
288 
289 
290 class NS3CommonSettingsTestCase(unittest.TestCase):
291  """!
292  ns3 tests related to generic options
293  """
294 
295  def setUp(self):
296  """!
297  Clean configuration/build artifacts before common commands
298  @return None
299  """
300  super().setUp()
301  # No special setup for common test cases other than making sure we are working on a clean directory.
302  run_ns3("clean")
303 
304  def test_01_NoOption(self):
305  """!
306  Test not passing any arguments to
307  @return None
308  """
309  return_code, stdout, stderr = run_ns3("")
310  self.assertEqual(return_code, 1)
311  self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
312 
314  """!
315  Test only passing --quiet argument to ns3
316  @return None
317  """
318  return_code, stdout, stderr = run_ns3("--quiet")
319  self.assertEqual(return_code, 1)
320  self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
321 
323  """!
324  Test only passing 'show config' argument to ns3
325  @return None
326  """
327  return_code, stdout, stderr = run_ns3("show config")
328  self.assertEqual(return_code, 1)
329  self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
330 
332  """!
333  Test only passing 'show profile' argument to ns3
334  @return None
335  """
336  return_code, stdout, stderr = run_ns3("show profile")
337  self.assertEqual(return_code, 1)
338  self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
339 
341  """!
342  Test only passing 'show version' argument to ns3
343  @return None
344  """
345  return_code, stdout, stderr = run_ns3("show version")
346  self.assertEqual(return_code, 1)
347  self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
348 
349 
350 class NS3ConfigureBuildProfileTestCase(unittest.TestCase):
351  """!
352  ns3 tests related to build profiles
353  """
354 
355  def setUp(self):
356  """!
357  Clean configuration/build artifacts before testing configuration settings
358  @return None
359  """
360  super().setUp()
361  # No special setup for common test cases other than making sure we are working on a clean directory.
362  run_ns3("clean")
363 
364  def test_01_Debug(self):
365  """!
366  Test the debug build
367  @return None
368  """
369  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d debug --enable-verbose")
370  self.assertEqual(return_code, 0)
371  self.assertIn("Build profile : debug", stdout)
372  self.assertIn("Build files have been written to", stdout)
373 
374  # Build core to check if profile suffixes match the expected.
375  return_code, stdout, stderr = run_ns3("build core")
376  self.assertEqual(return_code, 0)
377  self.assertIn("Built target libcore", stdout)
378 
379  libraries = get_libraries_list()
380  self.assertGreater(len(libraries), 0)
381  self.assertIn("core-debug", libraries[0])
382 
383  def test_02_Release(self):
384  """!
385  Test the release build
386  @return None
387  """
388  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d release")
389  self.assertEqual(return_code, 0)
390  self.assertIn("Build profile : release", stdout)
391  self.assertIn("Build files have been written to", stdout)
392 
393  def test_03_Optimized(self):
394  """!
395  Test the optimized build
396  @return None
397  """
398  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d optimized --enable-verbose")
399  self.assertEqual(return_code, 0)
400  self.assertIn("Build profile : optimized", stdout)
401  self.assertIn("Build files have been written to", stdout)
402 
403  # Build core to check if profile suffixes match the expected
404  return_code, stdout, stderr = run_ns3("build core")
405  self.assertEqual(return_code, 0)
406  self.assertIn("Built target libcore", stdout)
407 
408  libraries = get_libraries_list()
409  self.assertGreater(len(libraries), 0)
410  self.assertIn("core-optimized", libraries[0])
411 
412  def test_04_Typo(self):
413  """!
414  Test a build type with a typo
415  @return None
416  """
417  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d Optimized")
418  self.assertEqual(return_code, 2)
419  self.assertIn("invalid choice: 'Optimized'", stderr)
420 
421  def test_05_TYPO(self):
422  """!
423  Test a build type with another typo
424  @return None
425  """
426  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d OPTIMIZED")
427  self.assertEqual(return_code, 2)
428  self.assertIn("invalid choice: 'OPTIMIZED'", stderr)
429 
430 
431 class NS3BaseTestCase(unittest.TestCase):
432  """!
433  Generic test case with basic function inherited by more complex tests.
434  """
435 
436 
437  cleaned_once = False
438 
439  def config_ok(self, return_code, stdout):
440  """!
441  Check if configuration for release mode worked normally
442  @param return_code: return code from CMake
443  @param stdout: output from CMake.
444  @return None
445  """
446  self.assertEqual(return_code, 0)
447  self.assertIn("Build profile : release", stdout)
448  self.assertIn("Build files have been written to", stdout)
449 
450  def setUp(self):
451  """!
452  Clean configuration/build artifacts before testing configuration and build settings
453  After configuring the build as release,
454  check if configuration worked and check expected output files.
455  @return None
456  """
457  super().setUp()
458 
459  if os.path.exists(ns3rc_script):
460  os.remove(ns3rc_script)
461 
462  # We only clear it once and then update the settings by changing flags or consuming ns3rc.
463  if not NS3BaseTestCase.cleaned_once:
464  NS3BaseTestCase.cleaned_once = True
465  run_ns3("clean")
466  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d release --enable-verbose")
467  self.config_okconfig_ok(return_code, stdout)
468 
469  # Check if .lock-ns3 exists, then read to get list of executables.
470  self.assertTrue(os.path.exists(ns3_lock_filename))
471 
472  self.ns3_executablesns3_executables = get_programs_list()
473 
474  # Check if .lock-ns3 exists than read to get the list of enabled modules.
475  self.assertTrue(os.path.exists(ns3_lock_filename))
476 
477  self.ns3_modulesns3_modules = get_enabled_modules()
478 
479 
481  """!
482  Test ns3 configuration options
483  """
484 
485 
486  cleaned_once = False
487 
488  def setUp(self):
489  """!
490  Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
491  @return None
492  """
493  if not NS3ConfigureTestCase.cleaned_once:
494  NS3ConfigureTestCase.cleaned_once = True
495  NS3BaseTestCase.cleaned_once = False
496  super().setUp()
497 
498  def test_01_Examples(self):
499  """!
500  Test enabling and disabling examples
501  @return None
502  """
503  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples")
504 
505  # This just tests if we didn't break anything, not that we actually have enabled anything.
506  self.config_okconfig_ok(return_code, stdout)
507 
508  # If nothing went wrong, we should have more executables in the list after enabling the examples.
509  self.assertGreater(len(get_programs_list()), len(self.ns3_executablesns3_executables))
510 
511  # Now we disabled them back.
512  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-examples")
513 
514  # This just tests if we didn't break anything, not that we actually have enabled anything.
515  self.config_okconfig_ok(return_code, stdout)
516 
517  # Then check if they went back to the original list.
518  self.assertEqual(len(get_programs_list()), len(self.ns3_executablesns3_executables))
519 
520  def test_02_Tests(self):
521  """!
522  Test enabling and disabling tests
523  @return None
524  """
525  # Try enabling tests
526  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-tests")
527  self.config_okconfig_ok(return_code, stdout)
528 
529  # Then try building the libcore test
530  return_code, stdout, stderr = run_ns3("build core-test")
531 
532  # If nothing went wrong, this should have worked
533  self.assertEqual(return_code, 0)
534  self.assertIn("Built target libcore-test", stdout)
535 
536  # Now we disabled the tests
537  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-tests")
538  self.config_okconfig_ok(return_code, stdout)
539 
540  # Now building the library test should fail
541  return_code, stdout, stderr = run_ns3("build core-test")
542 
543  # Then check if they went back to the original list
544  self.assertEqual(return_code, 1)
545  self.assertIn("Target to build does not exist: core-test", stdout)
546 
548  """!
549  Test enabling specific modules
550  @return None
551  """
552  # Try filtering enabled modules to network+Wi-Fi and their dependencies
553  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-modules='network;wifi'")
554  self.config_okconfig_ok(return_code, stdout)
555 
556  # At this point we should have fewer modules
557  enabled_modules = get_enabled_modules()
558  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
559  self.assertIn("ns3-network", enabled_modules)
560  self.assertIn("ns3-wifi", enabled_modules)
561 
562  # Try enabling only core
563  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-modules='core'")
564  self.config_okconfig_ok(return_code, stdout)
565  self.assertIn("ns3-core", get_enabled_modules())
566 
567  # Try cleaning the list of enabled modules to reset to the normal configuration.
568  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-modules=''")
569  self.config_okconfig_ok(return_code, stdout)
570 
571  # At this point we should have the same amount of modules that we had when we started.
572  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
573 
575  """!
576  Test disabling specific modules
577  @return None
578  """
579  # Try filtering disabled modules to disable lte and modules that depend on it.
580  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-modules='lte;wimax'")
581  self.config_okconfig_ok(return_code, stdout)
582 
583  # At this point we should have fewer modules.
584  enabled_modules = get_enabled_modules()
585  self.assertLess(len(enabled_modules), len(self.ns3_modulesns3_modules))
586  self.assertNotIn("ns3-lte", enabled_modules)
587  self.assertNotIn("ns3-wimax", enabled_modules)
588 
589  # Try cleaning the list of enabled modules to reset to the normal configuration.
590  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-modules=''")
591  self.config_okconfig_ok(return_code, stdout)
592 
593  # At this point we should have the same amount of modules that we had when we started.
594  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
595 
597  """!
598  Test enabling comma-separated (waf-style) examples
599  @return None
600  """
601  # Try filtering enabled modules to network+Wi-Fi and their dependencies.
602  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-modules='network,wifi'")
603  self.config_okconfig_ok(return_code, stdout)
604 
605  # At this point we should have fewer modules.
606  enabled_modules = get_enabled_modules()
607  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
608  self.assertIn("ns3-network", enabled_modules)
609  self.assertIn("ns3-wifi", enabled_modules)
610 
611  # Try cleaning the list of enabled modules to reset to the normal configuration.
612  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-modules=''")
613  self.config_okconfig_ok(return_code, stdout)
614 
615  # At this point we should have the same amount of modules that we had when we started.
616  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
617 
619  """!
620  Test disabling comma-separated (waf-style) examples
621  @return None
622  """
623  # Try filtering disabled modules to disable lte and modules that depend on it.
624  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-modules='lte,mpi'")
625  self.config_okconfig_ok(return_code, stdout)
626 
627  # At this point we should have fewer modules.
628  enabled_modules = get_enabled_modules()
629  self.assertLess(len(enabled_modules), len(self.ns3_modulesns3_modules))
630  self.assertNotIn("ns3-lte", enabled_modules)
631  self.assertNotIn("ns3-mpi", enabled_modules)
632 
633  # Try cleaning the list of enabled modules to reset to the normal configuration.
634  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-modules=''")
635  self.config_okconfig_ok(return_code, stdout)
636 
637  # At this point we should have the same amount of modules that we had when we started.
638  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
639 
640  def test_07_Ns3rc(self):
641  """!
642  Test loading settings from the ns3rc config file
643  @return None
644  """
645  ns3rc_template = "# ! /usr/bin/env python\
646  \
647  # A list of the modules that will be enabled when ns-3 is run.\
648  # Modules that depend on the listed modules will be enabled also.\
649  #\
650  # All modules can be enabled by choosing 'all_modules'.\
651  modules_enabled = [{modules}]\
652  \
653  # Set this equal to true if you want examples to be run.\
654  examples_enabled = {examples}\
655  \
656  # Set this equal to true if you want tests to be run.\
657  tests_enabled = {tests}\
658  "
659 
660  # Now we repeat the command line tests but with the ns3rc file.
661  with open(ns3rc_script, "w") as f:
662  f.write(ns3rc_template.format(modules="'lte'", examples="False", tests="True"))
663 
664  # Reconfigure.
665  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
666  self.config_okconfig_ok(return_code, stdout)
667 
668  # Check.
669  enabled_modules = get_enabled_modules()
670  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
671  self.assertIn("ns3-lte", enabled_modules)
672  self.assertTrue(get_test_enabled())
673  self.assertEqual(len(get_programs_list()), len(self.ns3_executablesns3_executables))
674 
675  # Replace the ns3rc file with the wifi module, enabling examples and disabling tests
676  with open(ns3rc_script, "w") as f:
677  f.write(ns3rc_template.format(modules="'wifi'", examples="True", tests="False"))
678 
679  # Reconfigure
680  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
681  self.config_okconfig_ok(return_code, stdout)
682 
683  # Check
684  enabled_modules = get_enabled_modules()
685  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
686  self.assertIn("ns3-wifi", enabled_modules)
687  self.assertFalse(get_test_enabled())
688  self.assertGreater(len(get_programs_list()), len(self.ns3_executablesns3_executables))
689 
690  # Replace the ns3rc file with multiple modules
691  with open(ns3rc_script, "w") as f:
692  f.write(ns3rc_template.format(modules="'core','network'", examples="True", tests="False"))
693 
694  # Reconfigure
695  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
696  self.config_okconfig_ok(return_code, stdout)
697 
698  # Check
699  enabled_modules = get_enabled_modules()
700  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
701  self.assertIn("ns3-core", enabled_modules)
702  self.assertIn("ns3-network", enabled_modules)
703  self.assertFalse(get_test_enabled())
704  self.assertGreater(len(get_programs_list()), len(self.ns3_executablesns3_executables))
705 
706  # Replace the ns3rc file with multiple modules,
707  # in various different ways and with comments
708  with open(ns3rc_script, "w") as f:
709  f.write(ns3rc_template.format(modules="""'core', #comment
710  'lte',
711  #comment2,
712  #comment3
713  'network', 'internet','wimax'""", examples="True", tests="True"))
714 
715  # Reconfigure
716  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
717  self.config_okconfig_ok(return_code, stdout)
718 
719  # Check
720  enabled_modules = get_enabled_modules()
721  self.assertLess(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
722  self.assertIn("ns3-core", enabled_modules)
723  self.assertIn("ns3-internet", enabled_modules)
724  self.assertIn("ns3-lte", enabled_modules)
725  self.assertIn("ns3-wimax", enabled_modules)
726  self.assertTrue(get_test_enabled())
727  self.assertGreater(len(get_programs_list()), len(self.ns3_executablesns3_executables))
728 
729  # Then we roll back by removing the ns3rc config file
730  os.remove(ns3rc_script)
731 
732  # Reconfigure
733  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
734  self.config_okconfig_ok(return_code, stdout)
735 
736  # Check
737  self.assertEqual(len(get_enabled_modules()), len(self.ns3_modulesns3_modules))
738  self.assertFalse(get_test_enabled())
739  self.assertEqual(len(get_programs_list()), len(self.ns3_executablesns3_executables))
740 
741  def test_08_DryRun(self):
742  """!
743  Test dry-run (printing commands to be executed instead of running them)
744  @return None
745  """
746  run_ns3("clean")
747 
748  # Try dry-run before and after the positional commands (outputs should match)
749  for positional_command in ["configure", "build", "clean"]:
750  return_code, stdout, stderr = run_ns3("--dry-run %s" % positional_command)
751  return_code1, stdout1, stderr1 = run_ns3("%s --dry-run" % positional_command)
752 
753  self.assertEqual(return_code, return_code1)
754  self.assertEqual(stdout, stdout1)
755  self.assertEqual(stderr, stderr1)
756 
757  run_ns3("clean")
758 
759  # Build target before using below
760  run_ns3("configure -G \"Unix Makefiles\" -d release --enable-verbose")
761  run_ns3("build scratch-simulator")
762 
763  # Run all cases and then check outputs
764  return_code0, stdout0, stderr0 = run_ns3("--dry-run run scratch-simulator")
765  return_code1, stdout1, stderr1 = run_ns3("run scratch-simulator")
766  return_code2, stdout2, stderr2 = run_ns3("--dry-run run scratch-simulator --no-build")
767  return_code3, stdout3, stderr3 = run_ns3("run scratch-simulator --no-build")
768 
769  # Return code and stderr should be the same for all of them.
770  self.assertEqual(sum([return_code0, return_code1, return_code2, return_code3]), 0)
771  self.assertEqual([stderr0, stderr1, stderr2, stderr3], [""] * 4)
772 
773  scratch_path = None
774  for program in get_programs_list():
775  if "scratch-simulator" in program and "subdir" not in program:
776  scratch_path = program
777  break
778 
779  # Scratches currently have a 'scratch_' prefix in their CMake targets
780  # Case 0: dry-run + run (should print commands to build target and then run)
781  self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout0)
782  self.assertIn(scratch_path, stdout0)
783 
784  # Case 1: run (should print only make build message)
785  self.assertNotIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout1)
786  self.assertIn("Built target", stdout1)
787  self.assertNotIn(scratch_path, stdout1)
788 
789  # Case 2: dry-run + run-no-build (should print commands to run the target)
790  self.assertIn("The following commands would be executed:", stdout2)
791  self.assertIn(scratch_path, stdout2)
792 
793  # Case 3: run-no-build (should print the target output only)
794  self.assertNotIn("Finished executing the following commands:", stdout3)
795  self.assertNotIn(scratch_path, stdout3)
796 
798  """!
799  Test if ns3 is propagating back the return code from the executables called with the run command
800  @return None
801  """
802  # From this point forward we are reconfiguring in debug mode
803  return_code, _, _ = run_ns3("clean")
804  self.assertEqual(return_code, 0)
805 
806  return_code, _, _ = run_ns3("configure -G \"Unix Makefiles\" --enable-examples --enable-tests")
807  self.assertEqual(return_code, 0)
808 
809  # Build necessary executables
810  return_code, stdout, stderr = run_ns3("build command-line-example test-runner")
811  self.assertEqual(return_code, 0)
812 
813  # Now some tests will succeed normally
814  return_code, stdout, stderr = run_ns3("run \"test-runner --test-name=command-line\" --no-build")
815  self.assertEqual(return_code, 0)
816 
817  # Now some tests will fail during NS_COMMANDLINE_INTROSPECTION
818  return_code, stdout, stderr = run_ns3("run \"test-runner --test-name=command-line\" --no-build",
819  env={"NS_COMMANDLINE_INTROSPECTION": ".."}
820  )
821  self.assertNotEqual(return_code, 0)
822 
823  # Cause a sigsegv
824  sigsegv_example = os.path.join(ns3_path, "scratch", "sigsegv.cc")
825  with open(sigsegv_example, "w") as f:
826  f.write("""
827  int main (int argc, char *argv[])
828  {
829  char *s = "hello world"; *s = 'H';
830  return 0;
831  }
832  """)
833  return_code, stdout, stderr = run_ns3("run sigsegv")
834  self.assertEqual(return_code, 245)
835  self.assertIn("sigsegv-default' died with <Signals.SIGSEGV: 11>", stdout)
836 
837  # Cause an abort
838  abort_example = os.path.join(ns3_path, "scratch", "abort.cc")
839  with open(abort_example, "w") as f:
840  f.write("""
841  #include "ns3/core-module.h"
842 
843  using namespace ns3;
844  int main (int argc, char *argv[])
845  {
846  NS_ABORT_IF(true);
847  return 0;
848  }
849  """)
850  return_code, stdout, stderr = run_ns3("run abort")
851  self.assertEqual(return_code, 250)
852  self.assertIn("abort-default' died with <Signals.SIGABRT: 6>", stdout)
853 
854  os.remove(sigsegv_example)
855  os.remove(abort_example)
856 
858  """!
859  Test passing 'show config' argument to ns3 to get the configuration table
860  @return None
861  """
862  return_code, stdout, stderr = run_ns3("show config")
863  self.assertEqual(return_code, 0)
864  self.assertIn("Summary of optional ns-3 features", stdout)
865 
867  """!
868  Test passing 'show profile' argument to ns3 to get the build profile
869  @return None
870  """
871  return_code, stdout, stderr = run_ns3("show profile")
872  self.assertEqual(return_code, 0)
873  self.assertIn("Build profile: default", stdout)
874 
876  """!
877  Test passing 'show version' argument to ns3 to get the build version
878  @return None
879  """
880  if shutil.which("git") is None:
881  self.skipTest("git is not available")
882 
883  return_code, _, _ = run_ns3("configure -G \"Unix Makefiles\" --enable-build-version")
884  self.assertEqual(return_code, 0)
885 
886  return_code, stdout, stderr = run_ns3("show version")
887  self.assertEqual(return_code, 0)
888  self.assertIn("ns-3 version:", stdout)
889 
890  def test_13_Scratches(self):
891  """!
892  Test if CMake target names for scratches and ns3 shortcuts
893  are working correctly
894  @return None
895  """
896 
897  test_files = ["scratch/main.cc",
898  "scratch/empty.cc",
899  "scratch/subdir1/main.cc",
900  "scratch/subdir2/main.cc"]
901 
902  # Create test scratch files
903  for path in test_files:
904  filepath = os.path.join(ns3_path, path)
905  os.makedirs(os.path.dirname(filepath), exist_ok=True)
906  with open(filepath, "w") as f:
907  if "main" in path:
908  f.write("int main (int argc, char *argv[]){}")
909  else:
910  # no main function will prevent this target from
911  # being created, we should skip it and continue
912  # processing without crashing
913  f.write("")
914 
915  # Reload the cmake cache to pick them up
916  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
917  self.assertEqual(return_code, 0)
918 
919  # Try to build them with ns3 and cmake
920  for path in test_files:
921  path = path.replace(".cc", "")
922  return_code1, stdout1, stderr1 = run_program("cmake", "--build . --target %s"
923  % path.replace("/", "_"),
924  cwd=os.path.join(ns3_path, "cmake-cache"))
925  return_code2, stdout2, stderr2 = run_ns3("build %s" % path)
926  if "main" in path:
927  self.assertEqual(return_code1, 0)
928  self.assertEqual(return_code2, 0)
929  else:
930  self.assertEqual(return_code1, 2)
931  self.assertEqual(return_code2, 1)
932 
933  # Try to run them
934  for path in test_files:
935  path = path.replace(".cc", "")
936  return_code, stdout, stderr = run_ns3("run %s --no-build" % path)
937  if "main" in path:
938  self.assertEqual(return_code, 0)
939  else:
940  self.assertEqual(return_code, 1)
941 
942  # Delete the test files and reconfigure to clean them up
943  for path in test_files:
944  source_absolute_path = os.path.join(ns3_path, path)
945  os.remove(source_absolute_path)
946  if "empty" in path:
947  continue
948  filename = os.path.basename(path).replace(".cc", "")
949  executable_absolute_path = os.path.dirname(os.path.join(ns3_path, "build", path))
950  executable_name = list(filter(lambda x: filename in x,
951  os.listdir(executable_absolute_path)
952  )
953  )[0]
954 
955  os.remove(os.path.join(executable_absolute_path, executable_name))
956  if path not in ["scratch/main.cc", "scratch/empty.cc"]:
957  os.rmdir(os.path.dirname(source_absolute_path))
958 
959  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
960  self.assertEqual(return_code, 0)
961 
963  """!
964  Test if ns3 is inserting additional arguments by MPICH and OpenMPI to run on the CI
965  @return None
966  """
967  # Skip test if mpi is not installed
968  if shutil.which("mpiexec") is None:
969  self.skipTest("Mpi is not available")
970 
971  # Ensure sample simulator was built
972  return_code, stdout, stderr = run_ns3("build sample-simulator")
973  self.assertEqual(return_code, 0)
974 
975  # Get executable path
976  sample_simulator_path = list(filter(lambda x: "sample-simulator" in x, self.ns3_executablesns3_executables))[0]
977 
978  mpi_command = "--dry-run run sample-simulator --command-template=\"mpiexec -np 2 %s\""
979  non_mpi_command = "--dry-run run sample-simulator --command-template=\"echo %s\""
980 
981  # Get the commands to run sample-simulator in two processes with mpi
982  return_code, stdout, stderr = run_ns3(mpi_command)
983  self.assertEqual(return_code, 0)
984  self.assertIn("mpiexec -np 2 %s" % sample_simulator_path, stdout)
985 
986  # Get the commands to run sample-simulator in two processes with mpi, now with the environment variable
987  return_code, stdout, stderr = run_ns3(mpi_command, env={"MPI_CI": "1"})
988  self.assertEqual(return_code, 0)
989  if shutil.which("ompi_info"):
990  self.assertIn("mpiexec --allow-run-as-root --oversubscribe -np 2 %s" % sample_simulator_path, stdout)
991  else:
992  self.assertIn("mpiexec --allow-run-as-root -np 2 %s" % sample_simulator_path, stdout)
993 
994  # Now we repeat for the non-mpi command
995  return_code, stdout, stderr = run_ns3(non_mpi_command)
996  self.assertEqual(return_code, 0)
997  self.assertIn("echo %s" % sample_simulator_path, stdout)
998 
999  # Again the non-mpi command, with the MPI_CI environment variable set
1000  return_code, stdout, stderr = run_ns3(non_mpi_command, env={"MPI_CI": "1"})
1001  self.assertEqual(return_code, 0)
1002  self.assertIn("echo %s" % sample_simulator_path, stdout)
1003 
1005  """!
1006  Test if CMake and ns3 fail in the expected ways when:
1007  - examples from modules or general examples fail if they depend on a
1008  library with a name shorter than 4 characters or are disabled when
1009  a library is non-existant
1010  - a module library passes the configuration but fails to build due to
1011  a missing library
1012  @return None
1013  """
1014  os.makedirs("contrib/borked", exist_ok=True)
1015  os.makedirs("contrib/borked/examples", exist_ok=True)
1016 
1017  # Test if configuration succeeds and building the module library fails
1018  with open("contrib/borked/examples/CMakeLists.txt", "w") as f:
1019  f.write("")
1020  for invalid_or_non_existant_library in ["", "gsd", "lib", "libfi", "calibre"]:
1021  with open("contrib/borked/CMakeLists.txt", "w") as f:
1022  f.write("""
1023  build_lib(
1024  LIBNAME borked
1025  SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1026  LIBRARIES_TO_LINK ${libcore} %s
1027  )
1028  """ % invalid_or_non_existant_library)
1029 
1030  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples")
1031  if invalid_or_non_existant_library in ["", "gsd", "libfi", "calibre"]:
1032  self.assertEqual(return_code, 0)
1033  elif invalid_or_non_existant_library in ["lib"]:
1034  self.assertEqual(return_code, 1)
1035  self.assertIn("Invalid library name: %s" % invalid_or_non_existant_library, stderr)
1036  else:
1037  pass
1038 
1039  return_code, stdout, stderr = run_ns3("build borked")
1040  if invalid_or_non_existant_library in [""]:
1041  self.assertEqual(return_code, 0)
1042  elif invalid_or_non_existant_library in ["lib"]:
1043  self.assertEqual(return_code, 2) # should fail due to invalid library name
1044  self.assertIn("Invalid library name: %s" % invalid_or_non_existant_library, stderr)
1045  elif invalid_or_non_existant_library in ["gsd", "libfi", "calibre"]:
1046  self.assertEqual(return_code, 2) # should fail due to missing library
1047  self.assertIn("cannot find -l%s" % invalid_or_non_existant_library, stderr)
1048  else:
1049  pass
1050 
1051  # Now test if the example can be built with:
1052  # - no additional library (should work)
1053  # - invalid library names (should fail to configure)
1054  # - valid library names but inexistent libraries (should not create a target)
1055  with open("contrib/borked/CMakeLists.txt", "w") as f:
1056  f.write("""
1057  build_lib(
1058  LIBNAME borked
1059  SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1060  LIBRARIES_TO_LINK ${libcore}
1061  )
1062  """)
1063  for invalid_or_non_existant_library in ["", "gsd", "lib", "libfi", "calibre"]:
1064  with open("contrib/borked/examples/CMakeLists.txt", "w") as f:
1065  f.write("""
1066  build_lib_example(
1067  NAME borked-example
1068  SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty-main.cc
1069  LIBRARIES_TO_LINK ${libborked} %s
1070  )
1071  """ % invalid_or_non_existant_library)
1072 
1073  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
1074  if invalid_or_non_existant_library in ["", "gsd", "libfi", "calibre"]:
1075  self.assertEqual(return_code, 0) # should be able to configure
1076  elif invalid_or_non_existant_library in ["lib"]:
1077  self.assertEqual(return_code, 1) # should fail to even configure
1078  self.assertIn("Invalid library name: %s" % invalid_or_non_existant_library, stderr)
1079  else:
1080  pass
1081 
1082  return_code, stdout, stderr = run_ns3("build borked-example")
1083  if invalid_or_non_existant_library in [""]:
1084  self.assertEqual(return_code, 0) # should be able to build
1085  elif invalid_or_non_existant_library in ["libf"]:
1086  self.assertEqual(return_code, 2) # should fail due to missing configuration
1087  self.assertIn("Invalid library name: %s" % invalid_or_non_existant_library, stderr)
1088  elif invalid_or_non_existant_library in ["gsd", "libfi", "calibre"]:
1089  self.assertEqual(return_code, 1) # should fail to find target
1090  self.assertIn("Target to build does not exist: borked-example", stdout)
1091  else:
1092  pass
1093 
1094  shutil.rmtree("contrib/borked", ignore_errors=True)
1095 
1097  """!
1098  Test if CMake can properly handle modules containing "lib",
1099  which is used internally as a prefix for module libraries
1100  @return None
1101  """
1102 
1103  os.makedirs("contrib/calibre", exist_ok=True)
1104  os.makedirs("contrib/calibre/examples", exist_ok=True)
1105 
1106  # Now test if we can have a library with "lib" in it
1107  with open("contrib/calibre/examples/CMakeLists.txt", "w") as f:
1108  f.write("")
1109  with open("contrib/calibre/CMakeLists.txt", "w") as f:
1110  f.write("""
1111  build_lib(
1112  LIBNAME calibre
1113  SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1114  LIBRARIES_TO_LINK ${libcore}
1115  )
1116  """)
1117 
1118  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
1119 
1120  # This only checks if configuration passes
1121  self.assertEqual(return_code, 0)
1122 
1123  # This checks if the contrib modules were printed correctly
1124  self.assertIn("calibre", stdout)
1125 
1126  # This checks not only if "lib" from "calibre" was incorrectly removed,
1127  # but also if the pkgconfig file was generated with the correct name
1128  self.assertNotIn("care", stdout)
1129  self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "pkgconfig", "ns3-calibre.pc")))
1130 
1131  # Check if we can build this library
1132  return_code, stdout, stderr = run_ns3("build calibre")
1133  self.assertEqual(return_code, 0)
1134  self.assertIn(cmake_build_target_command(target="libcalibre"), stdout)
1135 
1136  shutil.rmtree("contrib/calibre", ignore_errors=True)
1137 
1139  """!
1140  Test if CMake performance tracing works and produces the
1141  cmake_performance_trace.log file
1142  @return None
1143  """
1144  return_code, stdout, stderr = run_ns3("configure --trace-performance")
1145  self.assertEqual(return_code, 0)
1146  self.assertIn("--profiling-format=google-trace --profiling-output=../cmake_performance_trace.log", stdout)
1147  self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake_performance_trace.log")))
1148 
1149 
1151  """!
1152  Tests ns3 regarding building the project
1153  """
1154 
1155 
1156  cleaned_once = False
1157 
1158  def setUp(self):
1159  """!
1160  Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
1161  @return None
1162  """
1163  if not NS3BuildBaseTestCase.cleaned_once:
1164  NS3BuildBaseTestCase.cleaned_once = True
1165  NS3BaseTestCase.cleaned_once = False
1166  super().setUp()
1167 
1168  self.ns3_librariesns3_libraries = get_libraries_list()
1169 
1171  """!
1172  Try building the core library
1173  @return None
1174  """
1175  return_code, stdout, stderr = run_ns3("build core")
1176  self.assertEqual(return_code, 0)
1177  self.assertIn("Built target libcore", stdout)
1178 
1180  """!
1181  Try building core-test library without tests enabled
1182  @return None
1183  """
1184  # tests are not enabled, so the target isn't available
1185  return_code, stdout, stderr = run_ns3("build core-test")
1186  self.assertEqual(return_code, 1)
1187  self.assertIn("Target to build does not exist: core-test", stdout)
1188 
1190  """!
1191  Try building the project:
1192  @return None
1193  """
1194  return_code, stdout, stderr = run_ns3("build")
1195  self.assertEqual(return_code, 0)
1196  self.assertIn("Built target", stdout)
1197  for program in get_programs_list():
1198  self.assertTrue(os.path.exists(program))
1199  self.assertIn(cmake_build_project_command, stdout)
1200 
1202  """!
1203  Try hiding task lines
1204  @return None
1205  """
1206  return_code, stdout, stderr = run_ns3("--quiet build")
1207  self.assertEqual(return_code, 0)
1208  self.assertIn(cmake_build_project_command, stdout)
1209 
1211  """!
1212  Try removing an essential file to break the build
1213  @return None
1214  """
1215  # change an essential file to break the build.
1216  attribute_cc_path = os.sep.join([ns3_path, "src", "core", "model", "attribute.cc"])
1217  attribute_cc_bak_path = attribute_cc_path + ".bak"
1218  shutil.move(attribute_cc_path, attribute_cc_bak_path)
1219 
1220  # build should break.
1221  return_code, stdout, stderr = run_ns3("build")
1222  self.assertNotEqual(return_code, 0)
1223 
1224  # move file back.
1225  shutil.move(attribute_cc_bak_path, attribute_cc_path)
1226 
1227  # build should work again.
1228  return_code, stdout, stderr = run_ns3("build")
1229  self.assertEqual(return_code, 0)
1230 
1232  """!
1233  Test if changing the version file affects the library names
1234  @return None
1235  """
1236  version_file = os.sep.join([ns3_path, "VERSION"])
1237  with open(version_file, "w") as f:
1238  f.write("3-00\n")
1239 
1240  # Reconfigure.
1241  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
1242  self.config_okconfig_ok(return_code, stdout)
1243 
1244  # Build.
1245  return_code, stdout, stderr = run_ns3("build")
1246  self.assertEqual(return_code, 0)
1247  self.assertIn("Built target", stdout)
1248 
1249  # Programs with new versions.
1250  new_programs = get_programs_list()
1251 
1252  # Check if they exist.
1253  for program in new_programs:
1254  self.assertTrue(os.path.exists(program))
1255 
1256  # Check if we still have the same number of binaries.
1257  self.assertEqual(len(new_programs), len(self.ns3_executablesns3_executablesns3_executables))
1258 
1259  # Check if versions changed from 3-dev to 3-00.
1260  libraries = get_libraries_list()
1261  new_libraries = list(set(libraries).difference(set(self.ns3_librariesns3_libraries)))
1262  self.assertEqual(len(new_libraries), len(self.ns3_librariesns3_libraries))
1263  for library in new_libraries:
1264  self.assertNotIn("libns3-dev", library)
1265  self.assertIn("libns3-00", library)
1266  self.assertTrue(os.path.exists(library))
1267 
1268  # Restore version file.
1269  with open(version_file, "w") as f:
1270  f.write("3-dev\n")
1271 
1272  # Reset flag to let it clean the build.
1273  NS3BuildBaseTestCase.cleaned_once = False
1274 
1276  """!
1277  Try setting a different output directory and if everything is
1278  in the right place and still working correctly
1279  @return None
1280  """
1281  # Re-build to return to the original state.
1282  run_ns3("build")
1283 
1284 
1285  self.ns3_librariesns3_libraries = get_libraries_list()
1286 
1287 
1289 
1290  # Delete built programs and libraries to check if they were restored later.
1291  for program in self.ns3_executablesns3_executablesns3_executables:
1292  os.remove(program)
1293  for library in self.ns3_librariesns3_libraries:
1294  os.remove(library)
1295 
1296  # Reconfigure setting the output folder to ns-3-dev/build/release (both as an absolute path or relative).
1297  absolute_path = os.sep.join([ns3_path, "build", "release"])
1298  relative_path = os.sep.join(["build", "release"])
1299  for different_out_dir in [absolute_path, relative_path]:
1300  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --out=%s" % different_out_dir)
1301  self.config_okconfig_ok(return_code, stdout)
1302  self.assertIn("Build directory : %s" % absolute_path, stdout)
1303 
1304  # Build
1305  run_ns3("build")
1306 
1307  # Check if we have the same number of binaries and that they were built correctly.
1308  new_programs = get_programs_list()
1309  self.assertEqual(len(new_programs), len(self.ns3_executablesns3_executablesns3_executables))
1310  for program in new_programs:
1311  self.assertTrue(os.path.exists(program))
1312 
1313  # Check if we have the same number of libraries and that they were built correctly.
1314  libraries = get_libraries_list(os.sep.join([absolute_path, "lib"]))
1315  new_libraries = list(set(libraries).difference(set(self.ns3_librariesns3_libraries)))
1316  self.assertEqual(len(new_libraries), len(self.ns3_librariesns3_libraries))
1317  for library in new_libraries:
1318  self.assertTrue(os.path.exists(library))
1319 
1320  # Remove files in the different output dir.
1321  shutil.rmtree(absolute_path)
1322 
1323  # Restore original output directory.
1324  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --out=''")
1325  self.config_okconfig_ok(return_code, stdout)
1326  self.assertIn("Build directory : %s" % usual_outdir, stdout)
1327 
1328  # Try re-building.
1329  run_ns3("build")
1330 
1331  # Check if we have the same binaries we had at the beginning.
1332  new_programs = get_programs_list()
1333  self.assertEqual(len(new_programs), len(self.ns3_executablesns3_executablesns3_executables))
1334  for program in new_programs:
1335  self.assertTrue(os.path.exists(program))
1336 
1337  # Check if we have the same libraries we had at the beginning.
1338  libraries = get_libraries_list()
1339  self.assertEqual(len(libraries), len(self.ns3_librariesns3_libraries))
1340  for library in libraries:
1341  self.assertTrue(os.path.exists(library))
1342 
1344  """!
1345  Tries setting a ns3 version, then installing it.
1346  After that, tries searching for ns-3 with CMake's find_package(ns3).
1347  Finally, tries using core library in a 3rd-party project
1348  @return None
1349  """
1350  # Remove existing libraries from the previous step.
1351  libraries = get_libraries_list()
1352  for library in libraries:
1353  os.remove(library)
1354 
1355  # 3-dev version format is not supported by CMake, so we use 3.01.
1356  version_file = os.sep.join([ns3_path, "VERSION"])
1357  with open(version_file, "w") as f:
1358  f.write("3-01\n")
1359 
1360  # Reconfigure setting the installation folder to ns-3-dev/build/install.
1361  install_prefix = os.sep.join([ns3_path, "build", "install"])
1362  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --prefix=%s" % install_prefix)
1363  self.config_okconfig_ok(return_code, stdout)
1364 
1365  # Build.
1366  run_ns3("build")
1367  libraries = get_libraries_list()
1368  headers = get_headers_list()
1369 
1370  # Install.
1371  run_ns3("install")
1372 
1373  # Find out if libraries were installed to lib or lib64 (Fedora thing).
1374  lib64 = os.path.exists(os.sep.join([install_prefix, "lib64"]))
1375  installed_libdir = os.sep.join([install_prefix, ("lib64" if lib64 else "lib")])
1376 
1377  # Make sure all libraries were installed.
1378  installed_libraries = get_libraries_list(installed_libdir)
1379  installed_libraries_list = ";".join(installed_libraries)
1380  for library in libraries:
1381  library_name = os.path.basename(library)
1382  self.assertIn(library_name, installed_libraries_list)
1383 
1384  # Make sure all headers were installed.
1385  installed_headers = get_headers_list(install_prefix)
1386  missing_headers = list(set([os.path.basename(x) for x in headers])
1387  - (set([os.path.basename(x) for x in installed_headers]))
1388  )
1389  self.assertEqual(len(missing_headers), 0)
1390 
1391  # Now create a test CMake project and try to find_package ns-3.
1392  test_main_file = os.sep.join([install_prefix, "main.cpp"])
1393  with open(test_main_file, "w") as f:
1394  f.write("""
1395  #include <ns3/core-module.h>
1396  using namespace ns3;
1397  int main ()
1398  {
1399  Simulator::Stop (Seconds (1.0));
1400  Simulator::Run ();
1401  Simulator::Destroy ();
1402  return 0;
1403  }
1404  """)
1405 
1406  # We try to use this library without specifying a version,
1407  # specifying ns3-01 (text version with 'dev' is not supported)
1408  # and specifying ns3-00 (a wrong version)
1409  for version in ["", "3.01", "3.00"]:
1410  ns3_import_methods = []
1411 
1412  # Import ns-3 libraries with as a CMake package
1413  cmake_find_package_import = """
1414  list(APPEND CMAKE_PREFIX_PATH ./{lib}/cmake/ns3)
1415  find_package(ns3 {version} COMPONENTS libcore)
1416  target_link_libraries(test PRIVATE ns3::libcore)
1417  """.format(lib=("lib64" if lib64 else "lib"), version=version)
1418  ns3_import_methods.append(cmake_find_package_import)
1419 
1420  # Import ns-3 as pkg-config libraries
1421  pkgconfig_import = """
1422  list(APPEND CMAKE_PREFIX_PATH ./)
1423  include(FindPkgConfig)
1424  pkg_check_modules(ns3 REQUIRED IMPORTED_TARGET ns3-core{version})
1425  target_link_libraries(test PUBLIC PkgConfig::ns3)
1426  """.format(lib=("lib64" if lib64 else "lib"),
1427  version="=" + version if version else ""
1428  )
1429  if shutil.which("pkg-config"):
1430  ns3_import_methods.append(pkgconfig_import)
1431 
1432  # Test the multiple ways of importing ns-3 libraries
1433  for import_method in ns3_import_methods:
1434  test_cmake_project = """
1435  cmake_minimum_required(VERSION 3.10..3.10)
1436  project(ns3_consumer CXX)
1437  set(CMAKE_CXX_STANDARD 17)
1438  set(CMAKE_CXX_STANDARD_REQUIRED ON)
1439  add_executable(test main.cpp)
1440  """ + import_method
1441 
1442  test_cmake_project_file = os.sep.join([install_prefix, "CMakeLists.txt"])
1443  with open(test_cmake_project_file, "w") as f:
1444  f.write(test_cmake_project)
1445 
1446  # Configure the test project
1447  cmake = shutil.which("cmake")
1448  return_code, stdout, stderr = run_program(cmake,
1449  "-DCMAKE_BUILD_TYPE=debug .",
1450  cwd=install_prefix)
1451  if version == "3.00":
1452  self.assertEqual(return_code, 1)
1453  if import_method == cmake_find_package_import:
1454  self.assertIn('Could not find a configuration file for package "ns3" that is compatible',
1455  stderr.replace("\n", ""))
1456  elif import_method == pkgconfig_import:
1457  self.assertIn('A required package was not found',
1458  stderr.replace("\n", ""))
1459  else:
1460  raise Exception("Unknown import type")
1461  else:
1462  self.assertEqual(return_code, 0)
1463  self.assertIn("Build files", stdout)
1464 
1465  # Build the test project making use of import ns-3
1466  return_code, stdout, stderr = run_program("cmake", "--build .", cwd=install_prefix)
1467 
1468  if version == "3.00":
1469  self.assertEqual(return_code, 2)
1470  self.assertGreater(len(stderr), 0)
1471  else:
1472  self.assertEqual(return_code, 0)
1473  self.assertIn("Built target", stdout)
1474 
1475  # Try running the test program that imports ns-3
1476  return_code, stdout, stderr = run_program("./test", "", cwd=install_prefix)
1477  self.assertEqual(return_code, 0)
1478 
1479  # Uninstall
1480  return_code, stdout, stderr = run_ns3("uninstall")
1481  self.assertIn("Built target uninstall", stdout)
1482 
1483  # Restore 3-dev version file
1484  with open(version_file, "w") as f:
1485  f.write("3-dev\n")
1486 
1487  # Reset flag to let it clean the build
1488  NS3BuildBaseTestCase.cleaned_once = False
1489 
1491  """!
1492  Tries to build scratch-simulator and subdir/scratch-simulator-subdir
1493  @return None
1494  """
1495  # Build.
1496  targets = {"scratch/scratch-simulator": "scratch-simulator",
1497  "scratch/scratch-simulator.cc": "scratch-simulator",
1498  "scratch-simulator": "scratch-simulator",
1499  "scratch/subdir/scratch-simulator-subdir": "subdir_scratch-simulator-subdir",
1500  "subdir/scratch-simulator-subdir": "subdir_scratch-simulator-subdir",
1501  "scratch-simulator-subdir": "subdir_scratch-simulator-subdir",
1502  }
1503  for (target_to_run, target_cmake) in targets.items():
1504  # Test if build is working.
1505  build_line = "target scratch_%s" % target_cmake
1506  return_code, stdout, stderr = run_ns3("build %s" % target_to_run)
1507  self.assertEqual(return_code, 0)
1508  self.assertIn(build_line, stdout)
1509 
1510  # Test if run is working
1511  return_code, stdout, stderr = run_ns3("run %s --verbose" % target_to_run)
1512  self.assertEqual(return_code, 0)
1513  self.assertIn(build_line, stdout)
1514  stdout = stdout.replace("scratch_%s" % target_cmake, "") # remove build lines
1515  self.assertIn(target_to_run.split("/")[-1].replace(".cc", ""), stdout)
1516 
1517  NS3BuildBaseTestCase.cleaned_once = False
1518 
1520  """!
1521  Test if cmake is calling pybindgen through modulegen to generate
1522  the bindings source files correctly
1523  @return None
1524  """
1525 
1526  # Skip this test if pybindgen is not available
1527  try:
1528  import pybindgen
1529  except Exception:
1530  self.skipTest("Pybindgen is not available")
1531 
1532  # Check if the number of runnable python scripts is equal to 0
1533  python_scripts = read_lock_entry("ns3_runnable_scripts")
1534  self.assertEqual(len(python_scripts), 0)
1535 
1536  # First we enable python bindings
1537  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples --enable-tests --enable-python-bindings")
1538  self.assertEqual(return_code, 0)
1539 
1540  # Then look for python bindings sources
1541  core_bindings_generated_sources_path = os.path.join(ns3_path, "build", "src", "core", "bindings")
1542  core_bindings_sources_path = os.path.join(ns3_path, "src", "core", "bindings")
1543  core_bindings_path = os.path.join(ns3_path, "build", "bindings", "python", "ns")
1544  core_bindings_header = os.path.join(core_bindings_generated_sources_path, "ns3module.h")
1545  core_bindings_source = os.path.join(core_bindings_generated_sources_path, "ns3module.cc")
1546  self.assertTrue(os.path.exists(core_bindings_header))
1547  self.assertTrue(os.path.exists(core_bindings_source))
1548 
1549  # Then try to build the bindings for the core module
1550  return_code, stdout, stderr = run_ns3("build core-bindings")
1551  self.assertEqual(return_code, 0)
1552 
1553  # Then check if it was built
1554  self.assertGreater(len(list(filter(lambda x: "_core" in x, os.listdir(core_bindings_path)))), 0)
1555 
1556  # Now enable python bindings scanning
1557  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -- -DNS3_SCAN_PYTHON_BINDINGS=ON")
1558  self.assertEqual(return_code, 0)
1559 
1560  # Get the file status for the current scanned bindings
1561  bindings_sources = os.listdir(core_bindings_sources_path)
1562  bindings_sources = [os.path.join(core_bindings_sources_path, y) for y in bindings_sources]
1563  timestamps = {}
1564  [timestamps.update({x: os.stat(x)}) for x in bindings_sources]
1565 
1566  # Try to scan the bindings for the core module
1567  return_code, stdout, stderr = run_ns3("build core-apiscan")
1568  self.assertEqual(return_code, 0)
1569 
1570  # Check if they exist, are not empty and have a different timestamp
1571  generated_python_files = ["callbacks_list.py", "modulegen__gcc_LP64.py"]
1572  for binding_file in timestamps.keys():
1573  if os.path.basename(binding_file) in generated_python_files:
1574  self.assertTrue(os.path.exists(binding_file))
1575  self.assertGreater(os.stat(binding_file).st_size, 0)
1576  new_fstat = os.stat(binding_file)
1577  self.assertNotEqual(timestamps[binding_file].st_mtime, new_fstat.st_mtime)
1578 
1579  # Then delete the old bindings sources
1580  for f in os.listdir(core_bindings_generated_sources_path):
1581  os.remove(os.path.join(core_bindings_generated_sources_path, f))
1582 
1583  # Reconfigure to recreate the source files
1584  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
1585  self.assertEqual(return_code, 0)
1586 
1587  # Check again if they exist
1588  self.assertTrue(os.path.exists(core_bindings_header))
1589  self.assertTrue(os.path.exists(core_bindings_source))
1590 
1591  # Build the core bindings again
1592  return_code, stdout, stderr = run_ns3("build core-bindings")
1593  self.assertEqual(return_code, 0)
1594 
1595  # Then check if it was built
1596  self.assertGreater(len(list(filter(lambda x: "_core" in x, os.listdir(core_bindings_path)))), 0)
1597 
1598  # We are on python anyway, so we can just try to load it from here
1599  sys.path.insert(0, os.path.join(core_bindings_path, ".."))
1600  try:
1601  from ns import core
1602  except ImportError:
1603  self.assertTrue(True)
1604 
1605  # Check if ns3 can find and run the python example
1606  return_code, stdout, stderr = run_ns3("run sample-simulator.py")
1607  self.assertEqual(return_code, 0)
1608 
1609  # Check if test.py can find and run the python example
1610  return_code, stdout, stderr = run_program("./test.py", "-p src/core/examples/sample-simulator.py", python=True)
1611  self.assertEqual(return_code, 0)
1612 
1613  # Since python examples do not require recompilation,
1614  # test if we still can run the python examples after disabling examples
1615  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-examples")
1616  self.assertEqual(return_code, 0)
1617 
1618  # Check if the lock file has python runnable python scripts (it should)
1619  python_scripts = read_lock_entry("ns3_runnable_scripts")
1620  self.assertGreater(len(python_scripts), 0)
1621 
1622  # Try to run an example
1623  return_code, stdout, stderr = run_ns3("run sample-simulator.py")
1624  self.assertEqual(return_code, 0)
1625 
1626  NS3BuildBaseTestCase.cleaned_once = False
1627 
1629  """!
1630  Test if ns3 can alert correctly in case a shortcut collision happens
1631  @return None
1632  """
1633 
1634  # First enable examples
1635  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples")
1636  self.assertEqual(return_code, 0)
1637 
1638  # Copy second.cc from the tutorial examples to the scratch folder
1639  shutil.copy("./examples/tutorial/second.cc", "./scratch/second.cc")
1640 
1641  # Reconfigure to re-scan the scratches
1642  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples")
1643  self.assertEqual(return_code, 0)
1644 
1645  # Try to run second and collide
1646  return_code, stdout, stderr = run_ns3("build second")
1647  self.assertEqual(return_code, 1)
1648  self.assertIn(
1649  'Build target "second" is ambiguous. Try one of these: "scratch/second", "examples/tutorial/second"',
1650  stdout
1651  )
1652 
1653  # Try to run scratch/second and succeed
1654  return_code, stdout, stderr = run_ns3("build scratch/second")
1655  self.assertEqual(return_code, 0)
1656  self.assertIn(cmake_build_target_command(target="scratch_second"), stdout)
1657 
1658  # Try to run scratch/second and succeed
1659  return_code, stdout, stderr = run_ns3("build tutorial/second")
1660  self.assertEqual(return_code, 0)
1661  self.assertIn(cmake_build_target_command(target="second"), stdout)
1662 
1663  # Try to run second and collide
1664  return_code, stdout, stderr = run_ns3("run second")
1665  self.assertEqual(return_code, 1)
1666  self.assertIn(
1667  'Run target "second" is ambiguous. Try one of these: "scratch/second", "examples/tutorial/second"',
1668  stdout
1669  )
1670 
1671  # Try to run scratch/second and succeed
1672  return_code, stdout, stderr = run_ns3("run scratch/second")
1673  self.assertEqual(return_code, 0)
1674 
1675  # Try to run scratch/second and succeed
1676  return_code, stdout, stderr = run_ns3("run tutorial/second")
1677  self.assertEqual(return_code, 0)
1678 
1679  # Remove second
1680  os.remove("./scratch/second.cc")
1681 
1682  NS3BuildBaseTestCase.cleaned_once = False
1683 
1685  """!
1686  Test if we can build a static ns-3 library and link it to static programs
1687  @return None
1688  """
1689  # First enable examples and static build
1690  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples --disable-gtk --enable-static")
1691 
1692  # If configuration passes, we are half way done
1693  self.assertEqual(return_code, 0)
1694 
1695  # Then try to build one example
1696  return_code, stdout, stderr = run_ns3('build sample-simulator')
1697  self.assertEqual(return_code, 0)
1698  self.assertIn("Built target", stdout)
1699 
1700  # Maybe check the built binary for shared library references? Using objdump, otool, etc
1701  NS3BuildBaseTestCase.cleaned_once = False
1702 
1703 
1705  """!
1706  Tests ns3 usage in more realistic scenarios
1707  """
1708 
1709 
1710  cleaned_once = False
1711 
1712  def setUp(self):
1713  """!
1714  Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
1715  Here examples, tests and documentation are also enabled.
1716  @return None
1717  """
1718  if not NS3ExpectedUseTestCase.cleaned_once:
1719  NS3ExpectedUseTestCase.cleaned_once = True
1720  NS3BaseTestCase.cleaned_once = False
1721  super().setUp()
1722 
1723  # On top of the release build configured by NS3ConfigureTestCase, also enable examples, tests and docs.
1724  return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples --enable-tests")
1725  self.config_okconfig_ok(return_code, stdout)
1726 
1727  # Check if .lock-ns3 exists, then read to get list of executables.
1728  self.assertTrue(os.path.exists(ns3_lock_filename))
1729 
1730 
1732 
1733  # Check if .lock-ns3 exists than read to get the list of enabled modules.
1734  self.assertTrue(os.path.exists(ns3_lock_filename))
1735 
1736 
1738 
1740  """!
1741  Try to build the project
1742  @return None
1743  """
1744  return_code, stdout, stderr = run_ns3("build")
1745  self.assertEqual(return_code, 0)
1746  self.assertIn("Built target", stdout)
1747  for program in get_programs_list():
1748  self.assertTrue(os.path.exists(program))
1749  libraries = get_libraries_list()
1750  for module in get_enabled_modules():
1751  self.assertIn(module.replace("ns3-", ""), ";".join(libraries))
1752  self.assertIn(cmake_build_project_command, stdout)
1753 
1755  """!
1756  Try to build and run test-runner
1757  @return None
1758  """
1759  return_code, stdout, stderr = run_ns3('run "test-runner --list" --verbose')
1760  self.assertEqual(return_code, 0)
1761  self.assertIn("Built target test-runner", stdout)
1762  self.assertIn(cmake_build_target_command(target="test-runner"), stdout)
1763 
1765  """!
1766  Try to build and run a library
1767  @return None
1768  """
1769  return_code, stdout, stderr = run_ns3("run core") # this should not work
1770  self.assertEqual(return_code, 1)
1771  self.assertIn("Couldn't find the specified program: core", stderr)
1772 
1774  """!
1775  Try to build and run an unknown target
1776  @return None
1777  """
1778  return_code, stdout, stderr = run_ns3("run nonsense") # this should not work
1779  self.assertEqual(return_code, 1)
1780  self.assertIn("Couldn't find the specified program: nonsense", stderr)
1781 
1783  """!
1784  Try to run test-runner without building
1785  @return None
1786  """
1787  return_code, stdout, stderr = run_ns3('run "test-runner --list" --no-build --verbose')
1788  self.assertEqual(return_code, 0)
1789  self.assertNotIn("Built target test-runner", stdout)
1790  self.assertNotIn(cmake_build_target_command(target="test-runner"), stdout)
1791 
1793  """!
1794  Test ns3 fails to run a library
1795  @return None
1796  """
1797  return_code, stdout, stderr = run_ns3("run core --no-build") # this should not work
1798  self.assertEqual(return_code, 1)
1799  self.assertIn("Couldn't find the specified program: core", stderr)
1800 
1802  """!
1803  Test ns3 fails to run an unknown program
1804  @return None
1805  """
1806  return_code, stdout, stderr = run_ns3("run nonsense --no-build") # this should not work
1807  self.assertEqual(return_code, 1)
1808  self.assertIn("Couldn't find the specified program: nonsense", stderr)
1809 
1811  """!
1812  Test if scratch simulator is executed through gdb and lldb
1813  @return None
1814  """
1815  if shutil.which("gdb") is None:
1816  self.skipTest("Missing gdb")
1817 
1818  return_code, stdout, stderr = run_ns3("run scratch-simulator --gdb --verbose --no-build")
1819  self.assertEqual(return_code, 0)
1820  self.assertIn("scratch-simulator", stdout)
1821  self.assertIn("No debugging symbols found", stdout)
1822 
1824  """!
1825  Test if scratch simulator is executed through valgrind
1826  @return None
1827  """
1828  if shutil.which("valgrind") is None:
1829  self.skipTest("Missing valgrind")
1830 
1831  return_code, stdout, stderr = run_ns3("run scratch-simulator --valgrind --verbose --no-build")
1832  self.assertEqual(return_code, 0)
1833  self.assertIn("scratch-simulator", stderr)
1834  self.assertIn("Memcheck", stderr)
1835 
1837  """!
1838  Test the doxygen target that does trigger a full build
1839  @return None
1840  """
1841  if shutil.which("doxygen") is None:
1842  self.skipTest("Missing doxygen")
1843 
1844  doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
1845 
1846  doxygen_files = ["introspected-command-line.h", "introspected-doxygen.h"]
1847  for filename in doxygen_files:
1848  file_path = os.sep.join([doc_folder, filename])
1849  if os.path.exists(file_path):
1850  os.remove(file_path)
1851 
1852  # Rebuilding dot images is super slow, so not removing doxygen products
1853  # doxygen_build_folder = os.sep.join([doc_folder, "html"])
1854  # if os.path.exists(doxygen_build_folder):
1855  # shutil.rmtree(doxygen_build_folder)
1856 
1857  return_code, stdout, stderr = run_ns3("docs doxygen")
1858  self.assertEqual(return_code, 0)
1859  self.assertIn(cmake_build_target_command(target="doxygen"), stdout)
1860  self.assertIn("Built target doxygen", stdout)
1861 
1863  """!
1864  Test the doxygen target that doesn't trigger a full build
1865  @return None
1866  """
1867  if shutil.which("doxygen") is None:
1868  self.skipTest("Missing doxygen")
1869 
1870  # Rebuilding dot images is super slow, so not removing doxygen products
1871  # doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
1872  # doxygen_build_folder = os.sep.join([doc_folder, "html"])
1873  # if os.path.exists(doxygen_build_folder):
1874  # shutil.rmtree(doxygen_build_folder)
1875 
1876  return_code, stdout, stderr = run_ns3("docs doxygen-no-build")
1877  self.assertEqual(return_code, 0)
1878  self.assertIn(cmake_build_target_command(target="doxygen-no-build"), stdout)
1879  self.assertIn("Built target doxygen-no-build", stdout)
1880 
1882  """!
1883  Test every individual target for Sphinx-based documentation
1884  @return None
1885  """
1886  if shutil.which("sphinx-build") is None:
1887  self.skipTest("Missing sphinx")
1888 
1889  doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
1890 
1891  # For each sphinx doc target.
1892  for target in ["contributing", "manual", "models", "tutorial"]:
1893  # First we need to clean old docs, or it will not make any sense.
1894  doc_build_folder = os.sep.join([doc_folder, target, "build"])
1895  doc_temp_folder = os.sep.join([doc_folder, target, "source-temp"])
1896  if os.path.exists(doc_build_folder):
1897  shutil.rmtree(doc_build_folder)
1898  if os.path.exists(doc_temp_folder):
1899  shutil.rmtree(doc_temp_folder)
1900 
1901  # Build
1902  return_code, stdout, stderr = run_ns3("docs %s" % target)
1903  self.assertEqual(return_code, 0)
1904  self.assertIn(cmake_build_target_command(target="sphinx_%s" % target), stdout)
1905  self.assertIn("Built target sphinx_%s" % target, stdout)
1906 
1907  # Check if the docs output folder exists
1908  doc_build_folder = os.sep.join([doc_folder, target, "build"])
1909  self.assertTrue(os.path.exists(doc_build_folder))
1910 
1911  # Check if the all the different types are in place (latex, split HTML and single page HTML)
1912  for build_type in ["latex", "html", "singlehtml"]:
1913  self.assertTrue(os.path.exists(os.sep.join([doc_build_folder, build_type])))
1914 
1916  """!
1917  Test the documentation target that builds
1918  both doxygen and sphinx based documentation
1919  @return None
1920  """
1921  if shutil.which("doxygen") is None:
1922  self.skipTest("Missing doxygen")
1923  if shutil.which("sphinx-build") is None:
1924  self.skipTest("Missing sphinx")
1925 
1926  doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
1927 
1928  # First we need to clean old docs, or it will not make any sense.
1929 
1930  # Rebuilding dot images is super slow, so not removing doxygen products
1931  # doxygen_build_folder = os.sep.join([doc_folder, "html"])
1932  # if os.path.exists(doxygen_build_folder):
1933  # shutil.rmtree(doxygen_build_folder)
1934 
1935  for target in ["manual", "models", "tutorial"]:
1936  doc_build_folder = os.sep.join([doc_folder, target, "build"])
1937  if os.path.exists(doc_build_folder):
1938  shutil.rmtree(doc_build_folder)
1939 
1940  return_code, stdout, stderr = run_ns3("docs all")
1941  self.assertEqual(return_code, 0)
1942  self.assertIn(cmake_build_target_command(target="sphinx"), stdout)
1943  self.assertIn("Built target sphinx", stdout)
1944  self.assertIn(cmake_build_target_command(target="doxygen"), stdout)
1945  self.assertIn("Built target doxygen", stdout)
1946 
1948  """!
1949  Try to set ownership of scratch-simulator from current user to root,
1950  and change execution permissions
1951  @return None
1952  """
1953 
1954  # Test will be skipped if not defined
1955  sudo_password = os.getenv("SUDO_PASSWORD", None)
1956 
1957  # Skip test if variable containing sudo password is the default value
1958  if sudo_password is None:
1959  self.skipTest("SUDO_PASSWORD environment variable was not specified")
1960 
1961  enable_sudo = read_lock_entry("ENABLE_SUDO")
1962  self.assertFalse(enable_sudo is True)
1963 
1964  # First we run to ensure the program was built
1965  return_code, stdout, stderr = run_ns3('run scratch-simulator')
1966  self.assertEqual(return_code, 0)
1967  self.assertIn("Built target scratch_scratch-simulator", stdout)
1968  self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout)
1969  scratch_simulator_path = list(filter(lambda x: x if "scratch-simulator" in x else None,
1970  self.ns3_executablesns3_executablesns3_executables
1971  )
1972  )[-1]
1973  prev_fstat = os.stat(scratch_simulator_path) # we get the permissions before enabling sudo
1974 
1975  # Now try setting the sudo bits from the run subparser
1976  return_code, stdout, stderr = run_ns3('run scratch-simulator --enable-sudo',
1977  env={"SUDO_PASSWORD": sudo_password})
1978  self.assertEqual(return_code, 0)
1979  self.assertIn("Built target scratch_scratch-simulator", stdout)
1980  self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout)
1981  fstat = os.stat(scratch_simulator_path)
1982 
1983  import stat
1984  # If we are on Windows, these permissions mean absolutely nothing,
1985  # and on Fuse builds they might not make any sense, so we need to skip before failing
1986  likely_fuse_mount = ((prev_fstat.st_mode & stat.S_ISUID) == (fstat.st_mode & stat.S_ISUID)) and \
1987  prev_fstat.st_uid == 0
1988 
1989  if sys.platform == "win32" or likely_fuse_mount:
1990  self.skipTest("Windows or likely a FUSE mount")
1991 
1992  # If this is a valid platform, we can continue
1993  self.assertEqual(fstat.st_uid, 0) # check the file was correctly chown'ed by root
1994  self.assertEqual(fstat.st_mode & stat.S_ISUID, stat.S_ISUID) # check if normal users can run as sudo
1995 
1996  # Now try setting the sudo bits as a post-build step (as set by configure subparser)
1997  return_code, stdout, stderr = run_ns3('configure --enable-sudo')
1998  self.assertEqual(return_code, 0)
1999 
2000  # Check if it was properly set in the buildstatus file
2001  enable_sudo = read_lock_entry("ENABLE_SUDO")
2002  self.assertTrue(enable_sudo)
2003 
2004  # Remove old executables
2005  for executable in self.ns3_executablesns3_executablesns3_executables:
2006  if os.path.exists(executable):
2007  os.remove(executable)
2008 
2009  # Try to build and then set sudo bits as a post-build step
2010  return_code, stdout, stderr = run_ns3('build', env={"SUDO_PASSWORD": sudo_password})
2011  self.assertEqual(return_code, 0)
2012 
2013  # Check if commands are being printed for every target
2014  self.assertIn("chown root", stdout)
2015  self.assertIn("chmod u+s", stdout)
2016  for executable in self.ns3_executablesns3_executablesns3_executables:
2017  self.assertIn(os.path.basename(executable), stdout)
2018 
2019  # Check scratch simulator yet again
2020  fstat = os.stat(scratch_simulator_path)
2021  self.assertEqual(fstat.st_uid, 0) # check the file was correctly chown'ed by root
2022  self.assertEqual(fstat.st_mode & stat.S_ISUID, stat.S_ISUID) # check if normal users can run as sudo
2023 
2025  """!
2026  Check if command template is working
2027  @return None
2028  """
2029 
2030  # Command templates that are empty or do not have a '%s' should fail
2031  return_code0, stdout0, stderr0 = run_ns3('run sample-simulator --command-template')
2032  self.assertEqual(return_code0, 2)
2033  self.assertIn("argument --command-template: expected one argument", stderr0)
2034 
2035  return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template=" "')
2036  return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --command-template " "')
2037  return_code3, stdout3, stderr3 = run_ns3('run sample-simulator --command-template "echo "')
2038  self.assertEqual((return_code1, return_code2, return_code3), (1, 1, 1))
2039  self.assertIn("not all arguments converted during string formatting", stderr1)
2040  self.assertEqual(stderr1, stderr2)
2041  self.assertEqual(stderr2, stderr3)
2042 
2043  # Command templates with %s should at least continue and try to run the target
2044  return_code4, stdout4, _ = run_ns3('run sample-simulator --command-template "%s --PrintVersion" --verbose')
2045  return_code5, stdout5, _ = run_ns3('run sample-simulator --command-template="%s --PrintVersion" --verbose')
2046  self.assertEqual((return_code4, return_code5), (0, 0))
2047  self.assertIn("sample-simulator --PrintVersion", stdout4)
2048  self.assertIn("sample-simulator --PrintVersion", stdout5)
2049 
2051  """!
2052  Check if all flavors of different argument passing to
2053  executable targets are working
2054  @return None
2055  """
2056 
2057  # Test if all argument passing flavors are working
2058  return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help" --verbose')
2059  return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template="%s --help" --verbose')
2060  return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --verbose -- --help')
2061 
2062  self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
2063  self.assertIn("sample-simulator --help", stdout0)
2064  self.assertIn("sample-simulator --help", stdout1)
2065  self.assertIn("sample-simulator --help", stdout2)
2066 
2067  # Test if the same thing happens with an additional run argument (e.g. --no-build)
2068  return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help" --no-build')
2069  return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template="%s --help" --no-build')
2070  return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --no-build -- --help')
2071  self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
2072  self.assertEqual(stdout0, stdout1)
2073  self.assertEqual(stdout1, stdout2)
2074  self.assertEqual(stderr0, stderr1)
2075  self.assertEqual(stderr1, stderr2)
2076 
2077  # Now collect results for each argument individually
2078  return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --PrintGlobals" --verbose')
2079  return_code1, stdout1, stderr1 = run_ns3('run "sample-simulator --PrintGroups" --verbose')
2080  return_code2, stdout2, stderr2 = run_ns3('run "sample-simulator --PrintTypeIds" --verbose')
2081 
2082  self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
2083  self.assertIn("sample-simulator --PrintGlobals", stdout0)
2084  self.assertIn("sample-simulator --PrintGroups", stdout1)
2085  self.assertIn("sample-simulator --PrintTypeIds", stdout2)
2086 
2087  # Then check if all the arguments are correctly merged by checking the outputs
2088  cmd = 'run "sample-simulator --PrintGlobals" --command-template="%s --PrintGroups" --verbose -- --PrintTypeIds'
2089  return_code, stdout, stderr = run_ns3(cmd)
2090  self.assertEqual(return_code, 0)
2091 
2092  # The order of the arguments is command template,
2093  # arguments passed with the target itself
2094  # and forwarded arguments after the -- separator
2095  self.assertIn("sample-simulator --PrintGroups --PrintGlobals --PrintTypeIds", stdout)
2096 
2097  # Check if it complains about the missing -- separator
2098  cmd0 = 'run sample-simulator --command-template="%s " --PrintTypeIds'
2099  cmd1 = 'run sample-simulator --PrintTypeIds'
2100 
2101  return_code0, stdout0, stderr0 = run_ns3(cmd0)
2102  return_code1, stdout1, stderr1 = run_ns3(cmd1)
2103  self.assertEqual((return_code0, return_code1), (1, 1))
2104  self.assertIn("To forward configuration or runtime options, put them after '--'", stderr0)
2105  self.assertIn("To forward configuration or runtime options, put them after '--'", stderr1)
2106 
2108  """!
2109  Test if scratch simulator is executed through lldb
2110  @return None
2111  """
2112  if shutil.which("lldb") is None:
2113  self.skipTest("Missing lldb")
2114 
2115  return_code, stdout, stderr = run_ns3("run scratch-simulator --lldb --verbose --no-build")
2116  self.assertEqual(return_code, 0)
2117  self.assertIn("scratch-simulator", stdout)
2118  self.assertIn("(lldb) target create", stdout)
2119 
2120 
2121 if __name__ == '__main__':
2122  loader = unittest.TestLoader()
2123  suite = unittest.TestSuite()
2124 
2125  # Put tests cases in order
2126  suite.addTests(loader.loadTestsFromTestCase(NS3UnusedSourcesTestCase))
2127  suite.addTests(loader.loadTestsFromTestCase(NS3CommonSettingsTestCase))
2128  suite.addTests(loader.loadTestsFromTestCase(NS3ConfigureBuildProfileTestCase))
2129  suite.addTests(loader.loadTestsFromTestCase(NS3ConfigureTestCase))
2130  suite.addTests(loader.loadTestsFromTestCase(NS3BuildBaseTestCase))
2131  suite.addTests(loader.loadTestsFromTestCase(NS3ExpectedUseTestCase))
2132 
2133  # Generate a dictionary of test names and their objects
2134  tests = dict(map(lambda x: (x._testMethodName, x), suite._tests))
2135 
2136  # Filter tests by name
2137  # name_to_search = ""
2138  # tests_to_run = set(map(lambda x: x if name_to_search in x else None, tests.keys()))
2139  # tests_to_remove = set(tests) - set(tests_to_run)
2140  # for test_to_remove in tests_to_remove:
2141  # suite._tests.remove(tests[test_to_remove])
2142 
2143  # Before running, check if ns3rc exists and save it
2144  ns3rc_script_bak = ns3rc_script + ".bak"
2145  if os.path.exists(ns3rc_script) and not os.path.exists(ns3rc_script_bak):
2146  shutil.move(ns3rc_script, ns3rc_script_bak)
2147 
2148  # Run tests and fail as fast as possible
2149  runner = unittest.TextTestRunner(failfast=True)
2150  result = runner.run(suite)
2151 
2152  # After completing the tests successfully, restore the ns3rc file
2153  if os.path.exists(ns3rc_script_bak):
2154  shutil.move(ns3rc_script_bak, ns3rc_script)
#define max(a, b)
Definition: 80211b.c:43
Generic test case with basic function inherited by more complex tests.
Definition: test-ns3.py:431
def config_ok(self, return_code, stdout)
Check if configuration for release mode worked normally.
Definition: test-ns3.py:439
ns3_executables
ns3_executables holds a list of executables in .lock-ns3
Definition: test-ns3.py:472
def setUp(self)
Clean configuration/build artifacts before testing configuration and build settings After configuring...
Definition: test-ns3.py:450
ns3_modules
ns3_modules holds a list to the modules enabled stored in .lock-ns3
Definition: test-ns3.py:477
Tests ns3 regarding building the project.
Definition: test-ns3.py:1150
def test_06_TestVersionFile(self)
Test if changing the version file affects the library names.
Definition: test-ns3.py:1231
def test_12_StaticBuilds(self)
Test if we can build a static ns-3 library and link it to static programs.
Definition: test-ns3.py:1684
def test_01_BuildExistingTargets(self)
Try building the core library.
Definition: test-ns3.py:1170
def test_10_PybindgenBindings(self)
Test if cmake is calling pybindgen through modulegen to generate the bindings source files correctly.
Definition: test-ns3.py:1519
def test_08_InstallationAndUninstallation(self)
Tries setting a ns3 version, then installing it.
Definition: test-ns3.py:1343
def test_11_AmbiguityCheck(self)
Test if ns3 can alert correctly in case a shortcut collision happens.
Definition: test-ns3.py:1628
def setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned.
Definition: test-ns3.py:1158
def test_02_BuildNonExistingTargets(self)
Try building core-test library without tests enabled.
Definition: test-ns3.py:1179
def test_04_BuildProjectNoTaskLines(self)
Try hiding task lines.
Definition: test-ns3.py:1201
def test_03_BuildProject(self)
Try building the project:
Definition: test-ns3.py:1189
def test_09_Scratches(self)
Tries to build scratch-simulator and subdir/scratch-simulator-subdir.
Definition: test-ns3.py:1490
def test_05_BreakBuild(self)
Try removing an essential file to break the build.
Definition: test-ns3.py:1210
ns3_executables
ns3_executables holds a list of executables in .lock-ns3
Definition: test-ns3.py:1288
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:1275
ns3_libraries
ns3_libraries holds a list of built module libraries
Definition: test-ns3.py:1168
ns3 tests related to generic options
Definition: test-ns3.py:290
def test_05_CheckVersion(self)
Test only passing 'show version' argument to ns3.
Definition: test-ns3.py:340
def setUp(self)
Clean configuration/build artifacts before common commands.
Definition: test-ns3.py:295
def test_01_NoOption(self)
Test not passing any arguments to.
Definition: test-ns3.py:304
def test_02_NoTaskLines(self)
Test only passing –quiet argument to ns3.
Definition: test-ns3.py:313
def test_03_CheckConfig(self)
Test only passing 'show config' argument to ns3.
Definition: test-ns3.py:322
def test_04_CheckProfile(self)
Test only passing 'show profile' argument to ns3.
Definition: test-ns3.py:331
ns3 tests related to build profiles
Definition: test-ns3.py:350
def test_05_TYPO(self)
Test a build type with another typo.
Definition: test-ns3.py:421
def test_02_Release(self)
Test the release build.
Definition: test-ns3.py:383
def test_01_Debug(self)
Test the debug build.
Definition: test-ns3.py:364
def setUp(self)
Clean configuration/build artifacts before testing configuration settings.
Definition: test-ns3.py:355
def test_03_Optimized(self)
Test the optimized build.
Definition: test-ns3.py:393
def test_04_Typo(self)
Test a build type with a typo.
Definition: test-ns3.py:412
Test ns3 configuration options.
Definition: test-ns3.py:480
def setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned.
Definition: test-ns3.py:488
def test_06_DisableModulesComma(self)
Test disabling comma-separated (waf-style) examples.
Definition: test-ns3.py:618
def test_04_DisableModules(self)
Test disabling specific modules.
Definition: test-ns3.py:574
def test_03_EnableModules(self)
Test enabling specific modules.
Definition: test-ns3.py:547
def test_02_Tests(self)
Test enabling and disabling tests.
Definition: test-ns3.py:520
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:797
def test_12_CheckVersion(self)
Test passing 'show version' argument to ns3 to get the build version.
Definition: test-ns3.py:875
def test_05_EnableModulesComma(self)
Test enabling comma-separated (waf-style) examples.
Definition: test-ns3.py:596
def test_01_Examples(self)
Test enabling and disabling examples.
Definition: test-ns3.py:498
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:962
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:1096
def test_17_CMakePerformanceTracing(self)
Test if CMake performance tracing works and produces the cmake_performance_trace.log file.
Definition: test-ns3.py:1138
def test_07_Ns3rc(self)
Test loading settings from the ns3rc config file.
Definition: test-ns3.py:640
def test_13_Scratches(self)
Test if CMake target names for scratches and ns3 shortcuts are working correctly.
Definition: test-ns3.py:890
def test_08_DryRun(self)
Test dry-run (printing commands to be executed instead of running them)
Definition: test-ns3.py:741
def test_10_CheckConfig(self)
Test passing 'show config' argument to ns3 to get the configuration table.
Definition: test-ns3.py:857
def test_15_InvalidLibrariesToLink(self)
Test if CMake and ns3 fail in the expected ways when:
Definition: test-ns3.py:1004
def test_11_CheckProfile(self)
Test passing 'show profile' argument to ns3 to get the build profile.
Definition: test-ns3.py:866
Tests ns3 usage in more realistic scenarios.
Definition: test-ns3.py:1704
def test_10_DoxygenWithBuild(self)
Test the doxygen target that does trigger a full build.
Definition: test-ns3.py:1836
def test_02_BuildAndRunExistingExecutableTarget(self)
Try to build and run test-runner.
Definition: test-ns3.py:1754
def test_08_RunNoBuildGdb(self)
Test if scratch simulator is executed through gdb and lldb.
Definition: test-ns3.py:1810
def test_05_RunNoBuildExistingExecutableTarget(self)
Try to run test-runner without building.
Definition: test-ns3.py:1782
def test_06_RunNoBuildExistingLibraryTarget(self)
Test ns3 fails to run a library.
Definition: test-ns3.py:1792
def test_03_BuildAndRunExistingLibraryTarget(self)
Try to build and run a library.
Definition: test-ns3.py:1764
def test_01_BuildProject(self)
Try to build the project.
Definition: test-ns3.py:1739
ns3_modules
ns3_modules holds a list to the modules enabled stored in .lock-ns3
Definition: test-ns3.py:1737
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:1947
def test_16_ForwardArgumentsToRunTargets(self)
Check if all flavors of different argument passing to executable targets are working.
Definition: test-ns3.py:2050
def test_17_RunNoBuildLldb(self)
Test if scratch simulator is executed through lldb.
Definition: test-ns3.py:2107
def test_15_CommandTemplate(self)
Check if command template is working.
Definition: test-ns3.py:2024
def test_04_BuildAndRunNonExistingTarget(self)
Try to build and run an unknown target.
Definition: test-ns3.py:1773
def test_07_RunNoBuildNonExistingExecutableTarget(self)
Test ns3 fails to run an unknown program.
Definition: test-ns3.py:1801
ns3_executables
ns3_executables holds a list of executables in .lock-ns3
Definition: test-ns3.py:1731
def test_09_RunNoBuildValgrind(self)
Test if scratch simulator is executed through valgrind.
Definition: test-ns3.py:1823
def test_13_Documentation(self)
Test the documentation target that builds both doxygen and sphinx based documentation.
Definition: test-ns3.py:1915
def setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned Here examples,...
Definition: test-ns3.py:1712
def test_11_DoxygenWithoutBuild(self)
Test the doxygen target that doesn't trigger a full build.
Definition: test-ns3.py:1862
def test_12_SphinxDocumentation(self)
Test every individual target for Sphinx-based documentation.
Definition: test-ns3.py:1881
ns-3 tests related to checking if source files were left behind, not being used by CMake
Definition: test-ns3.py:170
dictionary directory_and_files
dictionary containing directories with .cc source files
Definition: test-ns3.py:176
def test_01_UnusedExampleSources(self)
Test if all example source files are being used in their respective CMakeLists.txt.
Definition: test-ns3.py:194
def setUp(self)
Scan all C++ source files and add them to a list based on their path.
Definition: test-ns3.py:178
def test_02_UnusedModuleSources(self)
Test if all module source files are being used in their respective CMakeLists.txt.
Definition: test-ns3.py:217
def test_03_UnusedUtilsSources(self)
Test if all utils source files are being used in their respective CMakeLists.txt.
Definition: test-ns3.py:259
def get_programs_list()
Extracts the programs list from .lock-ns3.
Definition: test-ns3.py:113
def get_libraries_list(lib_outdir=usual_lib_outdir)
Gets a list of built libraries.
Definition: test-ns3.py:124
def get_test_enabled()
Check if tests are enabled in the .lock-ns3.
Definition: test-ns3.py:154
def read_lock_entry(entry)
Read interesting entries from the .lock-ns3 file.
Definition: test-ns3.py:142
cmake_build_target_command
Definition: test-ns3.py:47
def get_headers_list(outdir=usual_outdir)
Gets a list of header files.
Definition: test-ns3.py:133
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:68
def get_enabled_modules()
Definition: test-ns3.py:162
def run_ns3(args, env=None)
Runs the ns3 wrapper script with arguments.
Definition: test-ns3.py:52
#define list