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