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" % sample_simulator_path,
1474 stdout,
1475 )
1476 else:
1477 self.assertIn(
1478 "mpiexec --allow-run-as-root -np 2 %s" % sample_simulator_path, stdout
1479 )
1480 else:
1481 self.assertIn("mpiexec -np 2 %s" % sample_simulator_path, stdout)
1482
1483 # Now we repeat for the non-mpi command
1484 return_code, stdout, stderr = run_ns3(non_mpi_command)
1485 self.assertEqual(return_code, 0)
1486 self.assertIn("echo %s" % sample_simulator_path, stdout)
1487
1488 # Again the non-mpi command, with the MPI_CI environment variable set
1489 return_code, stdout, stderr = run_ns3(non_mpi_command)
1490 self.assertEqual(return_code, 0)
1491 self.assertIn("echo %s" % sample_simulator_path, stdout)
1492
1493 return_code, stdout, stderr = run_ns3('configure -G "{generator}" --disable-examples')
1494 self.assertEqual(return_code, 0)
1495
1497 """!
1498 Test if CMake and ns3 fail in the expected ways when:
1499 - examples from modules or general examples fail if they depend on a
1500 library with a name shorter than 4 characters or are disabled when
1501 a library is nonexistent
1502 - a module library passes the configuration but fails to build due to
1503 a missing library
1504 @return None
1505 """
1506 os.makedirs("contrib/borked", exist_ok=True)
1507 os.makedirs("contrib/borked/examples", exist_ok=True)
1508
1509 # Test if configuration succeeds and building the module library fails
1510 with open("contrib/borked/examples/CMakeLists.txt", "w", encoding="utf-8") as f:
1511 f.write("")
1512 for invalid_or_nonexistent_library in ["", "gsd", "lib", "libfi", "calibre"]:
1513 with open("contrib/borked/CMakeLists.txt", "w", encoding="utf-8") as f:
1514 f.write("""
1515 build_lib(
1516 LIBNAME borked
1517 SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1518 LIBRARIES_TO_LINK ${libcore} %s
1519 )
1520 """ % invalid_or_nonexistent_library)
1521
1522 return_code, stdout, stderr = run_ns3('configure -G "{generator}" --enable-examples')
1523 if invalid_or_nonexistent_library in ["", "gsd", "libfi", "calibre"]:
1524 self.assertEqual(return_code, 0)
1525 elif invalid_or_nonexistent_library in ["lib"]:
1526 self.assertEqual(return_code, 1)
1527 self.assertIn("Invalid library name: %s" % invalid_or_nonexistent_library, stderr)
1528 else:
1529 pass
1530
1531 return_code, stdout, stderr = run_ns3("build borked")
1532 if invalid_or_nonexistent_library in [""]:
1533 self.assertEqual(return_code, 0)
1534 elif invalid_or_nonexistent_library in ["lib"]:
1535 self.assertEqual(return_code, 2) # should fail due to invalid library name
1536 self.assertIn("Invalid library name: %s" % invalid_or_nonexistent_library, stderr)
1537 elif invalid_or_nonexistent_library in ["gsd", "libfi", "calibre"]:
1538 self.assertEqual(return_code, 2) # should fail due to missing library
1539 if "lld" in stdout + stderr:
1540 self.assertIn(
1541 "unable to find library -l%s" % invalid_or_nonexistent_library, stderr
1542 )
1543 elif "mold" in stdout + stderr:
1544 self.assertIn("library not found: %s" % invalid_or_nonexistent_library, stderr)
1545 elif macos:
1546 self.assertIn(
1547 "library not found for -l%s" % invalid_or_nonexistent_library, stderr
1548 )
1549 else:
1550 self.assertIn("cannot find -l%s" % invalid_or_nonexistent_library, stderr)
1551 else:
1552 pass
1553
1554 # Now test if the example can be built with:
1555 # - no additional library (should work)
1556 # - invalid library names (should fail to configure)
1557 # - valid library names but nonexistent libraries (should not create a target)
1558 with open("contrib/borked/CMakeLists.txt", "w", encoding="utf-8") as f:
1559 f.write("""
1560 build_lib(
1561 LIBNAME borked
1562 SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1563 LIBRARIES_TO_LINK ${libcore}
1564 )
1565 """)
1566 for invalid_or_nonexistent_library in ["", "gsd", "lib", "libfi", "calibre"]:
1567 with open("contrib/borked/examples/CMakeLists.txt", "w", encoding="utf-8") as f:
1568 f.write("""
1569 build_lib_example(
1570 NAME borked-example
1571 SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty-main.cc
1572 LIBRARIES_TO_LINK ${libborked} %s
1573 )
1574 """ % invalid_or_nonexistent_library)
1575
1576 return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1577 if invalid_or_nonexistent_library in ["", "gsd", "libfi", "calibre"]:
1578 self.assertEqual(return_code, 0) # should be able to configure
1579 elif invalid_or_nonexistent_library in ["lib"]:
1580 self.assertEqual(return_code, 1) # should fail to even configure
1581 self.assertIn("Invalid library name: %s" % invalid_or_nonexistent_library, stderr)
1582 else:
1583 pass
1584
1585 return_code, stdout, stderr = run_ns3("build borked-example")
1586 if invalid_or_nonexistent_library in [""]:
1587 self.assertEqual(return_code, 0) # should be able to build
1588 elif invalid_or_nonexistent_library in ["libf"]:
1589 self.assertEqual(return_code, 2) # should fail due to missing configuration
1590 self.assertIn("Invalid library name: %s" % invalid_or_nonexistent_library, stderr)
1591 elif invalid_or_nonexistent_library in ["gsd", "libfi", "calibre"]:
1592 self.assertEqual(return_code, 1) # should fail to find target
1593 self.assertIn("Target to build does not exist: borked-example", stdout)
1594 else:
1595 pass
1596
1597 shutil.rmtree("contrib/borked", ignore_errors=True)
1598
1600 """!
1601 Test if CMake can properly handle modules containing "lib",
1602 which is used internally as a prefix for module libraries
1603 @return None
1604 """
1605
1606 os.makedirs("contrib/calibre", exist_ok=True)
1607 os.makedirs("contrib/calibre/examples", exist_ok=True)
1608
1609 # Now test if we can have a library with "lib" in it
1610 with open("contrib/calibre/examples/CMakeLists.txt", "w", encoding="utf-8") as f:
1611 f.write("")
1612 with open("contrib/calibre/CMakeLists.txt", "w", encoding="utf-8") as f:
1613 f.write("""
1614 build_lib(
1615 LIBNAME calibre
1616 SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1617 LIBRARIES_TO_LINK ${libcore}
1618 )
1619 """)
1620
1621 return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
1622
1623 # This only checks if configuration passes
1624 self.assertEqual(return_code, 0)
1625
1626 # This checks if the contrib modules were printed correctly
1627 self.assertIn("calibre", stdout)
1628
1629 # This checks not only if "lib" from "calibre" was incorrectly removed,
1630 # but also if the pkgconfig file was generated with the correct name
1631 self.assertNotIn("care", stdout)
1632 self.assertTrue(
1633 os.path.exists(os.path.join(ns3_path, "cmake-cache", "pkgconfig", "ns3-calibre.pc"))
1634 )
1635
1636 # Check if we can build this library
1637 return_code, stdout, stderr = run_ns3("build calibre")
1638 self.assertEqual(return_code, 0)
1639 self.assertIn(cmake_build_target_command(target="calibre"), stdout)
1640
1641 shutil.rmtree("contrib/calibre", ignore_errors=True)
1642
1644 """!
1645 Test if CMake performance tracing works and produces the
1646 cmake_performance_trace.log file
1647 @return None
1648 """
1649 cmake_performance_trace_log = os.path.join(ns3_path, "cmake_performance_trace.log")
1650 if os.path.exists(cmake_performance_trace_log):
1651 os.remove(cmake_performance_trace_log)
1652
1653 return_code, stdout, stderr = run_ns3("configure --trace-performance")
1654 self.assertEqual(return_code, 0)
1655 if win32:
1656 self.assertIn("--profiling-format=google-trace --profiling-output=", stdout)
1657 else:
1658 self.assertIn(
1659 "--profiling-format=google-trace --profiling-output=./cmake_performance_trace.log",
1660 stdout,
1661 )
1662 self.assertTrue(os.path.exists(cmake_performance_trace_log))
1663
1665 """!
1666 Check if ENABLE_BUILD_VERSION and version.cache are working
1667 as expected
1668 @return None
1669 """
1670
1671 # Create Docker client instance and start it
1672 with DockerContainerManager(self, "ubuntu:22.04") as container:
1673 # Install basic packages
1674 container.execute("apt-get update")
1675 container.execute("apt-get install -y python3 ninja-build cmake g++")
1676
1677 # Clean ns-3 artifacts
1678 container.execute("./ns3 clean")
1679
1680 # Set path to version.cache file
1681 version_cache_file = os.path.join(ns3_path, "src/core/model/version.cache")
1682
1683 # First case: try without a version cache or Git
1684 if os.path.exists(version_cache_file):
1685 os.remove(version_cache_file)
1686
1687 # We need to catch the exception since the command will fail
1688 try:
1689 container.execute("./ns3 configure -G Ninja --enable-build-version")
1690 except DockerException:
1691 pass
1692 self.assertFalse(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1693
1694 # Second case: try with a version cache file but without Git (it should succeed)
1695 version_cache_contents = (
1696 "CLOSEST_TAG = '\"ns-3.0.0\"'\n"
1697 "VERSION_COMMIT_HASH = '\"0000000000\"'\n"
1698 "VERSION_DIRTY_FLAG = '0'\n"
1699 "VERSION_MAJOR = '3'\n"
1700 "VERSION_MINOR = '0'\n"
1701 "VERSION_PATCH = '0'\n"
1702 "VERSION_RELEASE_CANDIDATE = '\"\"'\n"
1703 "VERSION_TAG = '\"ns-3.0.0\"'\n"
1704 "VERSION_TAG_DISTANCE = '0'\n"
1705 "VERSION_BUILD_PROFILE = 'debug'\n"
1706 )
1707 with open(version_cache_file, "w", encoding="utf-8") as version:
1708 version.write(version_cache_contents)
1709
1710 # Configuration should now succeed
1711 container.execute("./ns3 clean")
1712 container.execute("./ns3 configure -G Ninja --enable-build-version")
1713 container.execute("./ns3 build core")
1714 self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1715
1716 # And contents of version cache should be unchanged
1717 with open(version_cache_file, "r", encoding="utf-8") as version:
1718 self.assertEqual(version.read(), version_cache_contents)
1719
1720 # Third case: we rename the .git directory temporarily and reconfigure
1721 # to check if it gets configured successfully when Git is found but
1722 # there is not .git history
1723 os.rename(os.path.join(ns3_path, ".git"), os.path.join(ns3_path, "temp_git"))
1724 try:
1725 container.execute("apt-get install -y git")
1726 container.execute("./ns3 clean")
1727 container.execute("./ns3 configure -G Ninja --enable-build-version")
1728 container.execute("./ns3 build core")
1729 except DockerException:
1730 pass
1731 os.rename(os.path.join(ns3_path, "temp_git"), os.path.join(ns3_path, ".git"))
1732 self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1733
1734 # Fourth case: test with Git and git history. Now the version.cache should be replaced.
1735 container.execute("./ns3 clean")
1736 container.execute("./ns3 configure -G Ninja --enable-build-version")
1737 container.execute("./ns3 build core")
1738 self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1739 with open(version_cache_file, "r", encoding="utf-8") as version:
1740 self.assertNotEqual(version.read(), version_cache_contents)
1741
1742 # Remove version cache file if it exists
1743 if os.path.exists(version_cache_file):
1744 os.remove(version_cache_file)
1745
1747 """!
1748 Test filtering in examples and tests from specific modules
1749 @return None
1750 """
1751 # Try filtering enabled modules to core+network and their dependencies
1752 return_code, stdout, stderr = run_ns3(
1753 'configure -G "{generator}" --enable-examples --enable-tests'
1754 )
1755 self.config_ok(return_code, stdout, stderr)
1756
1757 modules_before_filtering = get_enabled_modules()
1758 programs_before_filtering = get_programs_list()
1759
1760 return_code, stdout, stderr = run_ns3(
1761 "configure -G \"{generator}\" --filter-module-examples-and-tests='core;network'"
1762 )
1763 self.config_ok(return_code, stdout, stderr)
1764
1765 modules_after_filtering = get_enabled_modules()
1766 programs_after_filtering = get_programs_list()
1767
1768 # At this point we should have the same number of modules
1769 self.assertEqual(len(modules_after_filtering), len(modules_before_filtering))
1770 # But less executables
1771 self.assertLess(len(programs_after_filtering), len(programs_before_filtering))
1772
1773 # Try filtering in only core
1774 return_code, stdout, stderr = run_ns3(
1775 "configure -G \"{generator}\" --filter-module-examples-and-tests='core'"
1776 )
1777 self.config_ok(return_code, stdout, stderr)
1778
1779 # At this point we should have the same number of modules
1780 self.assertEqual(len(get_enabled_modules()), len(modules_after_filtering))
1781 # But less executables
1782 self.assertLess(len(get_programs_list()), len(programs_after_filtering))
1783
1784 # Try cleaning the list of enabled modules to reset to the normal configuration.
1785 return_code, stdout, stderr = run_ns3(
1786 "configure -G \"{generator}\" --disable-examples --disable-tests --filter-module-examples-and-tests=''"
1787 )
1788 self.config_ok(return_code, stdout, stderr)
1789
1790 # At this point we should have the same amount of modules that we had when we started.
1791 self.assertEqual(len(get_enabled_modules()), len(self.ns3_modules))
1792 self.assertEqual(len(get_programs_list()), len(self.ns3_executables))
1793
1795 """!
1796 Check if fast linkers LLD and Mold are correctly found and configured
1797 @return None
1798 """
1799
1800 run_ns3("clean")
1801 with DockerContainerManager(self, "gcc:12") as container:
1802 # Install basic packages
1803 container.execute("apt-get update")
1804 container.execute("apt-get install -y python3 ninja-build cmake g++ lld")
1805
1806 # Configure should detect and use lld
1807 container.execute("./ns3 configure -G Ninja")
1808
1809 # Check if configuration properly detected lld
1810 self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1811 with open(
1812 os.path.join(ns3_path, "cmake-cache", "build.ninja"), "r", encoding="utf-8"
1813 ) as f:
1814 self.assertIn("-fuse-ld=lld", f.read())
1815
1816 # Try to build using the lld linker
1817 try:
1818 container.execute("./ns3 build core")
1819 except DockerException:
1820 self.assertTrue(False, "Build with lld failed")
1821
1822 # Now add mold to the PATH
1823 if not os.path.exists(f"./mold-1.4.2-{arch}-linux.tar.gz"):
1824 container.execute(
1825 f"wget https://github.com/rui314/mold/releases/download/v1.4.2/mold-1.4.2-{arch}-linux.tar.gz"
1826 )
1827 container.execute(
1828 f"tar xzfC mold-1.4.2-{arch}-linux.tar.gz /usr/local --strip-components=1"
1829 )
1830
1831 # Configure should detect and use mold
1832 run_ns3("clean")
1833 container.execute("./ns3 configure -G Ninja")
1834
1835 # Check if configuration properly detected mold
1836 self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1837 with open(
1838 os.path.join(ns3_path, "cmake-cache", "build.ninja"), "r", encoding="utf-8"
1839 ) as f:
1840 self.assertIn("-fuse-ld=mold", f.read())
1841
1842 # Try to build using the lld linker
1843 try:
1844 container.execute("./ns3 build core")
1845 except DockerException:
1846 self.assertTrue(False, "Build with mold failed")
1847
1848 # Delete mold leftovers
1849 os.remove(f"./mold-1.4.2-{arch}-linux.tar.gz")
1850
1851 # Disable use of fast linkers
1852 container.execute("./ns3 configure -G Ninja -- -DNS3_FAST_LINKERS=OFF")
1853
1854 # Check if configuration properly disabled lld/mold usage
1855 self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "build.ninja")))
1856 with open(
1857 os.path.join(ns3_path, "cmake-cache", "build.ninja"), "r", encoding="utf-8"
1858 ) as f:
1859 self.assertNotIn("-fuse-ld=mold", f.read())
1860
1862 """!
1863 Check if NS3_CLANG_TIMETRACE feature is working
1864 Clang's -ftime-trace plus ClangAnalyzer report
1865 @return None
1866 """
1867
1868 run_ns3("clean")
1869 with DockerContainerManager(self, "ubuntu:24.04") as container:
1870 container.execute("apt-get update")
1871 container.execute("apt-get install -y python3 ninja-build cmake clang-18")
1872
1873 # Enable ClangTimeTrace without git (it should fail)
1874 try:
1875 container.execute(
1876 "./ns3 configure -G Ninja --enable-modules=core --enable-examples --enable-tests -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-18 -DNS3_CLANG_TIMETRACE=ON"
1877 )
1878 except DockerException as e:
1879 self.assertIn("could not find git for clone of ClangBuildAnalyzer", e.stderr)
1880
1881 container.execute("apt-get install -y git")
1882
1883 # Enable ClangTimeTrace without git (it should succeed)
1884 try:
1885 container.execute(
1886 "./ns3 configure -G Ninja --enable-modules=core --enable-examples --enable-tests -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-18 -DNS3_CLANG_TIMETRACE=ON"
1887 )
1888 except DockerException as e:
1889 self.assertIn("could not find git for clone of ClangBuildAnalyzer", e.stderr)
1890
1891 # Clean leftover time trace report
1892 time_trace_report_path = os.path.join(ns3_path, "ClangBuildAnalyzerReport.txt")
1893 if os.path.exists(time_trace_report_path):
1894 os.remove(time_trace_report_path)
1895
1896 # Build new time trace report
1897 try:
1898 container.execute("./ns3 build timeTraceReport")
1899 except DockerException as e:
1900 self.assertTrue(False, "Failed to build the ClangAnalyzer's time trace report")
1901
1902 # Check if the report exists
1903 self.assertTrue(os.path.exists(time_trace_report_path))
1904
1905 # Now try with GCC, which should fail during the configuration
1906 run_ns3("clean")
1907 container.execute("apt-get install -y g++")
1908 container.execute("apt-get remove -y clang-18")
1909
1910 try:
1911 container.execute(
1912 "./ns3 configure -G Ninja --enable-modules=core --enable-examples --enable-tests -- -DNS3_CLANG_TIMETRACE=ON"
1913 )
1914 self.assertTrue(
1915 False, "ClangTimeTrace requires Clang, but GCC just passed the checks too"
1916 )
1917 except DockerException as e:
1918 self.assertIn("TimeTrace is a Clang feature", e.stderr)
1919
1921 """!
1922 Check if NS3_NINJA_TRACE feature is working
1923 Ninja's .ninja_log conversion to about://tracing
1924 json format conversion with Ninjatracing
1925 @return None
1926 """
1927
1928 run_ns3("clean")
1929 with DockerContainerManager(self, "ubuntu:24.04") as container:
1930 container.execute("apt-get update")
1931 container.execute("apt-get remove -y g++")
1932 container.execute("apt-get install -y python3 cmake g++-11 clang-18")
1933
1934 # Enable Ninja tracing without using the Ninja generator
1935 try:
1936 container.execute(
1937 "./ns3 configure --enable-modules=core --enable-ninja-tracing -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-18"
1938 )
1939 except DockerException as e:
1940 self.assertIn("Ninjatracing requires the Ninja generator", e.stderr)
1941
1942 # Clean build system leftovers
1943 run_ns3("clean")
1944
1945 container.execute("apt-get install -y ninja-build")
1946 # Enable Ninjatracing support without git (should fail)
1947 try:
1948 container.execute(
1949 "./ns3 configure -G Ninja --enable-modules=core --enable-ninja-tracing -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-18"
1950 )
1951 except DockerException as e:
1952 self.assertIn("could not find git for clone of NinjaTracing", e.stderr)
1953
1954 container.execute("apt-get install -y git")
1955 # Enable Ninjatracing support with git (it should succeed)
1956 try:
1957 container.execute(
1958 "./ns3 configure -G Ninja --enable-modules=core --enable-ninja-tracing -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-18"
1959 )
1960 except DockerException as e:
1961 self.assertTrue(False, "Failed to configure with Ninjatracing")
1962
1963 # Clean leftover ninja trace
1964 ninja_trace_path = os.path.join(ns3_path, "ninja_performance_trace.json")
1965 if os.path.exists(ninja_trace_path):
1966 os.remove(ninja_trace_path)
1967
1968 # Build the core module
1969 container.execute("./ns3 build core")
1970
1971 # Build new ninja trace
1972 try:
1973 container.execute("./ns3 build ninjaTrace")
1974 except DockerException as e:
1975 self.assertTrue(False, "Failed to run Ninjatracing's tool to build the trace")
1976
1977 # Check if the report exists
1978 self.assertTrue(os.path.exists(ninja_trace_path))
1979 trace_size = os.stat(ninja_trace_path).st_size
1980 os.remove(ninja_trace_path)
1981
1982 run_ns3("clean")
1983
1984 # Enable Clang TimeTrace feature for more detailed traces
1985 try:
1986 container.execute(
1987 "./ns3 configure -G Ninja --enable-modules=core --enable-ninja-tracing -- -DCMAKE_CXX_COMPILER=/usr/bin/clang++-18 -DNS3_CLANG_TIMETRACE=ON"
1988 )
1989 except DockerException as e:
1990 self.assertTrue(False, "Failed to configure Ninjatracing with Clang's TimeTrace")
1991
1992 # Build the core module
1993 container.execute("./ns3 build core")
1994
1995 # Build new ninja trace
1996 try:
1997 container.execute("./ns3 build ninjaTrace")
1998 except DockerException as e:
1999 self.assertTrue(False, "Failed to run Ninjatracing's tool to build the trace")
2000
2001 self.assertTrue(os.path.exists(ninja_trace_path))
2002 timetrace_size = os.stat(ninja_trace_path).st_size
2003 os.remove(ninja_trace_path)
2004
2005 # Check if timetrace's trace is bigger than the original trace (it should be)
2006 self.assertGreater(timetrace_size, trace_size)
2007
2009 """!
2010 Check if precompiled headers are being enabled correctly.
2011 @return None
2012 """
2013
2014 run_ns3("clean")
2015
2016 # Ubuntu 22.04 ships with:
2017 # - cmake 3.22: does support PCH
2018 # - ccache 4.5: compatible with pch
2019 with DockerContainerManager(self, "ubuntu:22.04") as container:
2020 container.execute("apt-get update")
2021 container.execute("apt-get install -y python3 cmake ccache g++")
2022 try:
2023 container.execute("./ns3 configure")
2024 except DockerException as e:
2025 self.assertTrue(False, "Precompiled headers should have been enabled")
2026
2028 """!
2029 Check for regressions in test object build.
2030 @return None
2031 """
2032 return_code, stdout, stderr = run_ns3("configure")
2033 self.assertEqual(return_code, 0)
2034
2035 test_module_cache = os.path.join(ns3_path, "cmake-cache", "src", "test")
2036 self.assertFalse(os.path.exists(test_module_cache))
2037
2038 return_code, stdout, stderr = run_ns3("configure --enable-tests")
2039 self.assertEqual(return_code, 0)
2040 self.assertTrue(os.path.exists(test_module_cache))
2041
2043 """!
2044 Check for regressions in a bare ns-3 configuration.
2045 @return None
2046 """
2047
2048 run_ns3("clean")
2049
2050 with DockerContainerManager(self, "ubuntu:22.04") as container:
2051 container.execute("apt-get update")
2052 container.execute("apt-get install -y python3 cmake g++")
2053 return_code = 0
2054 stdout = ""
2055 try:
2056 stdout = container.execute("./ns3 configure -d release")
2057 except DockerException as e:
2058 return_code = 1
2059 self.config_ok(return_code, stdout, stdout)
2060
2061 run_ns3("clean")
2062
2063
2065 """!
2066 Tests ns3 regarding building the project
2067 """
2068
2069 def setUp(self):
2070 """!
2071 Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
2072 @return None
2073 """
2074 super().setUp()
2075
2077
2079 """!
2080 Try building the core library
2081 @return None
2082 """
2083 return_code, stdout, stderr = run_ns3("build core")
2084 self.assertEqual(return_code, 0)
2085 self.assertIn("Built target core", stdout)
2086
2088 """!
2089 Try building core-test library without tests enabled
2090 @return None
2091 """
2092 # tests are not enabled, so the target isn't available
2093 return_code, stdout, stderr = run_ns3("build core-test")
2094 self.assertEqual(return_code, 1)
2095 self.assertIn("Target to build does not exist: core-test", stdout)
2096
2098 """!
2099 Try building the project:
2100 @return None
2101 """
2102 return_code, stdout, stderr = run_ns3("build")
2103 self.assertEqual(return_code, 0)
2104 self.assertIn("Built target", stdout)
2105 for program in get_programs_list():
2106 self.assertTrue(os.path.exists(program), program)
2107 self.assertIn(cmake_build_project_command, stdout)
2108
2110 """!
2111 Try hiding task lines
2112 @return None
2113 """
2114 return_code, stdout, stderr = run_ns3("--quiet build")
2115 self.assertEqual(return_code, 0)
2116 self.assertIn(cmake_build_project_command, stdout)
2117
2119 """!
2120 Try removing an essential file to break the build
2121 @return None
2122 """
2123 # change an essential file to break the build.
2124 attribute_cc_path = os.sep.join([ns3_path, "src", "core", "model", "attribute.cc"])
2125 attribute_cc_bak_path = attribute_cc_path + ".bak"
2126 shutil.move(attribute_cc_path, attribute_cc_bak_path)
2127
2128 # build should break.
2129 return_code, stdout, stderr = run_ns3("build")
2130 self.assertNotEqual(return_code, 0)
2131
2132 # move file back.
2133 shutil.move(attribute_cc_bak_path, attribute_cc_path)
2134
2135 # Build should work again.
2136 return_code, stdout, stderr = run_ns3("build")
2137 self.assertEqual(return_code, 0)
2138
2140 """!
2141 Test if changing the version file affects the library names
2142 @return None
2143 """
2144 run_ns3("build")
2146
2147 version_file = os.sep.join([ns3_path, "VERSION"])
2148 with open(version_file, "w", encoding="utf-8") as f:
2149 f.write("3-00\n")
2150
2151 # Reconfigure.
2152 return_code, stdout, stderr = run_ns3('configure -G "{generator}"')
2153 self.config_ok(return_code, stdout, stderr)
2154
2155 # Build.
2156 return_code, stdout, stderr = run_ns3("build")
2157 self.assertEqual(return_code, 0)
2158 self.assertIn("Built target", stdout)
2159
2160 # Programs with new versions.
2161 new_programs = get_programs_list()
2162
2163 # Check if they exist.
2164 for program in new_programs:
2165 self.assertTrue(os.path.exists(program))
2166
2167 # Check if we still have the same number of binaries.
2168 self.assertEqual(len(new_programs), len(self.ns3_executables))
2169
2170 # Check if versions changed from 3-dev to 3-00.
2171 libraries = get_libraries_list()
2172 new_libraries = list(set(libraries).difference(set(self.ns3_libraries)))
2173 self.assertEqual(len(new_libraries), len(self.ns3_libraries))
2174 for library in new_libraries:
2175 self.assertNotIn("libns3-dev", library)
2176 self.assertIn("libns3-00", library)
2177 self.assertTrue(os.path.exists(library))
2178
2179 # Restore version file.
2180 with open(version_file, "w", encoding="utf-8") as f:
2181 f.write("3-dev\n")
2182
2184 """!
2185 Try setting a different output directory and if everything is
2186 in the right place and still working correctly
2187 @return None
2188 """
2189
2190 # Re-build to return to the original state.
2191 return_code, stdout, stderr = run_ns3("build")
2192 self.assertEqual(return_code, 0)
2193
2194 ## ns3_libraries holds a list of built module libraries # noqa
2196
2197 ## ns3_executables holds a list of executables in .lock-ns3 # noqa
2199
2200 # Delete built programs and libraries to check if they were restored later.
2201 for program in self.ns3_executables:
2202 os.remove(program)
2203 for library in self.ns3_libraries:
2204 os.remove(library)
2205
2206 # Reconfigure setting the output folder to ns-3-dev/build/release (both as an absolute path or relative).
2207 absolute_path = os.sep.join([ns3_path, "build", "release"])
2208 relative_path = os.sep.join(["build", "release"])
2209 for different_out_dir in [absolute_path, relative_path]:
2210 return_code, stdout, stderr = run_ns3(
2211 'configure -G "{generator}" --out="%s"' % different_out_dir
2212 )
2213 self.config_ok(return_code, stdout, stderr)
2214 self.assertIn(
2215 "Build directory : %s" % absolute_path.replace(os.sep, "/"), stdout
2216 )
2217
2218 # Build
2219 run_ns3("build")
2220
2221 # Check if we have the same number of binaries and that they were built correctly.
2222 new_programs = get_programs_list()
2223 self.assertEqual(len(new_programs), len(self.ns3_executables))
2224 for program in new_programs:
2225 self.assertTrue(os.path.exists(program))
2226
2227 # Check if we have the same number of libraries and that they were built correctly.
2228 libraries = get_libraries_list(os.sep.join([absolute_path, "lib"]))
2229 new_libraries = list(set(libraries).difference(set(self.ns3_libraries)))
2230 self.assertEqual(len(new_libraries), len(self.ns3_libraries))
2231 for library in new_libraries:
2232 self.assertTrue(os.path.exists(library))
2233
2234 # Remove files in the different output dir.
2235 shutil.rmtree(absolute_path)
2236
2237 # Restore original output directory.
2238 return_code, stdout, stderr = run_ns3("configure -G \"{generator}\" --out=''")
2239 self.config_ok(return_code, stdout, stderr)
2240 self.assertIn(
2241 "Build directory : %s" % usual_outdir.replace(os.sep, "/"), stdout
2242 )
2243
2244 # Try re-building.
2245 run_ns3("build")
2246
2247 # Check if we have the same binaries we had at the beginning.
2248 new_programs = get_programs_list()
2249 self.assertEqual(len(new_programs), len(self.ns3_executables))
2250 for program in new_programs:
2251 self.assertTrue(os.path.exists(program))
2252
2253 # Check if we have the same libraries we had at the beginning.
2254 libraries = get_libraries_list()
2255 self.assertEqual(len(libraries), len(self.ns3_libraries))
2256 for library in libraries:
2257 self.assertTrue(os.path.exists(library))
2258
2260 """!
2261 Tries setting a ns3 version, then installing it.
2262 After that, tries searching for ns-3 with CMake's find_package(ns3).
2263 Finally, tries using core library in a 3rd-party project
2264 @return None
2265 """
2266 # Remove existing libraries from the previous step.
2267 libraries = get_libraries_list()
2268 for library in libraries:
2269 os.remove(library)
2270
2271 # 3-dev version format is not supported by CMake, so we use 3.01.
2272 version_file = os.sep.join([ns3_path, "VERSION"])
2273 with open(version_file, "w", encoding="utf-8") as f:
2274 f.write("3-01\n")
2275
2276 # Reconfigure setting the installation folder to ns-3-dev/build/install.
2277 install_prefix = os.sep.join([ns3_path, "build", "install"])
2278 return_code, stdout, stderr = run_ns3(
2279 'configure -G "{generator}" --prefix="%s"' % install_prefix
2280 )
2281 self.config_ok(return_code, stdout, stderr)
2282
2283 # Build.
2284 run_ns3("build")
2285 libraries = get_libraries_list()
2286 headers = get_headers_list()
2287
2288 # Install.
2289 run_ns3("install")
2290
2291 # Find out if libraries were installed to lib or lib64 (Fedora thing).
2292 lib64 = os.path.exists(os.sep.join([install_prefix, "lib64"]))
2293 installed_libdir = os.sep.join([install_prefix, ("lib64" if lib64 else "lib")])
2294
2295 # Make sure all libraries were installed.
2296 installed_libraries = get_libraries_list(installed_libdir)
2297 installed_libraries_list = ";".join(installed_libraries)
2298 for library in libraries:
2299 library_name = os.path.basename(library)
2300 self.assertIn(library_name, installed_libraries_list)
2301
2302 # Make sure all headers were installed.
2303 installed_headers = get_headers_list(install_prefix)
2304 missing_headers = list(
2305 set([os.path.basename(x) for x in headers])
2306 - (set([os.path.basename(x) for x in installed_headers]))
2307 )
2308 self.assertEqual(len(missing_headers), 0)
2309
2310 # Now create a test CMake project and try to find_package ns-3.
2311 test_main_file = os.sep.join([install_prefix, "main.cpp"])
2312 with open(test_main_file, "w", encoding="utf-8") as f:
2313 f.write("""
2314 #include <ns3/core-module.h>
2315 using namespace ns3;
2316 int main ()
2317 {
2318 Simulator::Stop (Seconds (1.0));
2319 Simulator::Run ();
2320 Simulator::Destroy ();
2321 return 0;
2322 }
2323 """)
2324
2325 # We try to use this library without specifying a version,
2326 # specifying ns3-01 (text version with 'dev' is not supported)
2327 # and specifying ns3-00 (a wrong version)
2328 for version in ["", "3.01", "3.00"]:
2329 ns3_import_methods = []
2330
2331 # Import ns-3 libraries with as a CMake package
2332 cmake_find_package_import = """
2333 list(APPEND CMAKE_PREFIX_PATH ./{lib}/cmake/ns3)
2334 find_package(ns3 {version} COMPONENTS core)
2335 target_link_libraries(test PRIVATE ns3::core)
2336 """.format(lib=("lib64" if lib64 else "lib"), version=version)
2337 ns3_import_methods.append(cmake_find_package_import)
2338
2339 # Import ns-3 as pkg-config libraries
2340 pkgconfig_import = """
2341 list(APPEND CMAKE_PREFIX_PATH ./)
2342 include(FindPkgConfig)
2343 pkg_check_modules(ns3 REQUIRED IMPORTED_TARGET ns3-core{version})
2344 target_link_libraries(test PUBLIC PkgConfig::ns3)
2345 """.format(
2346 lib=("lib64" if lib64 else "lib"), version="=" + version if version else ""
2347 )
2348 if shutil.which("pkg-config"):
2349 ns3_import_methods.append(pkgconfig_import)
2350
2351 # Test the multiple ways of importing ns-3 libraries
2352 for import_method in ns3_import_methods:
2353 test_cmake_project = """
2354 cmake_minimum_required(VERSION 3.20..3.20)
2355 project(ns3_consumer CXX)
2356 set(CMAKE_CXX_STANDARD 23)
2357 set(CMAKE_CXX_STANDARD_REQUIRED ON)
2358 add_executable(test main.cpp)
2359 """ + import_method
2360
2361 test_cmake_project_file = os.sep.join([install_prefix, "CMakeLists.txt"])
2362 with open(test_cmake_project_file, "w", encoding="utf-8") as f:
2363 f.write(test_cmake_project)
2364
2365 # Configure the test project
2366 cmake = shutil.which("cmake")
2367 return_code, stdout, stderr = run_program(
2368 cmake,
2369 '-DCMAKE_BUILD_TYPE=debug -G"{generator}" .'.format(
2370 generator=platform_makefiles
2371 ),
2372 cwd=install_prefix,
2373 )
2374
2375 if version == "3.00":
2376 self.assertEqual(return_code, 1)
2377 if import_method == cmake_find_package_import:
2378 self.assertIn(
2379 'Could not find a configuration file for package "ns3" that is compatible',
2380 stderr.replace("\n", ""),
2381 )
2382 elif import_method == pkgconfig_import:
2383 self.assertIn("not found", stderr.replace("\n", ""))
2384 else:
2385 raise Exception("Unknown import type")
2386 else:
2387 self.assertEqual(return_code, 0)
2388 self.assertIn("Build files", stdout)
2389
2390 # Build the test project making use of import ns-3
2391 return_code, stdout, stderr = run_program("cmake", "--build .", cwd=install_prefix)
2392
2393 if version == "3.00":
2394 self.assertEqual(return_code, 2, msg=stdout + stderr)
2395 self.assertGreater(len(stderr), 0)
2396 else:
2397 self.assertEqual(return_code, 0)
2398 self.assertIn("Built target", stdout)
2399
2400 # Try running the test program that imports ns-3
2401 if win32:
2402 test_program = os.path.join(install_prefix, "test.exe")
2403 env_sep = ";" if ";" in os.environ["PATH"] else ":"
2404 env = {
2405 "PATH": env_sep.join(
2406 [os.environ["PATH"], os.path.join(install_prefix, "lib")]
2407 )
2408 }
2409 else:
2410 test_program = "./test"
2411 env = None
2412 return_code, stdout, stderr = run_program(
2413 test_program, "", cwd=install_prefix, env=env
2414 )
2415 self.assertEqual(return_code, 0)
2416
2417 # Uninstall
2418 return_code, stdout, stderr = run_ns3("uninstall")
2419 self.assertIn("Built target uninstall", stdout)
2420
2421 # Restore 3-dev version file
2422 os.remove(version_file)
2423 with open(version_file, "w", encoding="utf-8") as f:
2424 f.write("3-dev\n")
2425
2427 """!
2428 Tries to build scratch-simulator and subdir/scratch-simulator-subdir
2429 @return None
2430 """
2431 # Build.
2432 targets = {
2433 "scratch/scratch-simulator": "scratch-simulator",
2434 "scratch/scratch-simulator.cc": "scratch-simulator",
2435 "scratch-simulator": "scratch-simulator",
2436 "scratch/subdir/scratch-subdir": "subdir_scratch-subdir",
2437 "subdir/scratch-subdir": "subdir_scratch-subdir",
2438 "scratch-subdir": "subdir_scratch-subdir",
2439 }
2440 for target_to_run, target_cmake in targets.items():
2441 # Test if build is working.
2442 build_line = "target scratch_%s" % target_cmake
2443 return_code, stdout, stderr = run_ns3("build %s" % target_to_run)
2444 self.assertEqual(return_code, 0)
2445 self.assertIn(build_line, stdout)
2446
2447 # Test if run is working
2448 return_code, stdout, stderr = run_ns3("run %s --verbose" % target_to_run)
2449 self.assertEqual(return_code, 0)
2450 self.assertIn(build_line, stdout)
2451 stdout = stdout.replace("scratch_%s" % target_cmake, "") # remove build lines
2452 self.assertIn(target_to_run.split("/")[-1].replace(".cc", ""), stdout)
2453
2455 """!
2456 Test if ns3 can alert correctly in case a shortcut collision happens
2457 @return None
2458 """
2459
2460 # First enable examples
2461 return_code, stdout, stderr = run_ns3('configure -G "{generator}" --enable-examples')
2462 self.assertEqual(return_code, 0)
2463
2464 # Copy second.cc from the tutorial examples to the scratch folder
2465 shutil.copy("./examples/tutorial/second.cc", "./scratch/second.cc")
2466
2467 # Reconfigure to re-scan the scratches
2468 return_code, stdout, stderr = run_ns3('configure -G "{generator}" --enable-examples')
2469 self.assertEqual(return_code, 0)
2470
2471 # Try to run second and collide
2472 return_code, stdout, stderr = run_ns3("build second")
2473 self.assertEqual(return_code, 1)
2474 self.assertIn(
2475 'Build target "second" is ambiguous. Try one of these: "scratch/second", "examples/tutorial/second"',
2476 stdout.replace(os.sep, "/"),
2477 )
2478
2479 # Try to run scratch/second and succeed
2480 return_code, stdout, stderr = run_ns3("build scratch/second")
2481 self.assertEqual(return_code, 0)
2482 self.assertIn(cmake_build_target_command(target="scratch_second"), stdout)
2483
2484 # Try to run scratch/second and succeed
2485 return_code, stdout, stderr = run_ns3("build tutorial/second")
2486 self.assertEqual(return_code, 0)
2487 self.assertIn(cmake_build_target_command(target="second"), stdout)
2488
2489 # Try to run second and collide
2490 return_code, stdout, stderr = run_ns3("run second")
2491 self.assertEqual(return_code, 1)
2492 self.assertIn(
2493 'Run target "second" is ambiguous. Try one of these: "scratch/second", "examples/tutorial/second"',
2494 stdout.replace(os.sep, "/"),
2495 )
2496
2497 # Try to run scratch/second and succeed
2498 return_code, stdout, stderr = run_ns3("run scratch/second")
2499 self.assertEqual(return_code, 0)
2500
2501 # Try to run scratch/second and succeed
2502 return_code, stdout, stderr = run_ns3("run tutorial/second")
2503 self.assertEqual(return_code, 0)
2504
2505 # Remove second
2506 os.remove("./scratch/second.cc")
2507
2509 """!
2510 Test if we can build a static ns-3 library and link it to static programs
2511 @return None
2512 """
2513 if (not win32) and (arch == "aarch64"):
2514 if platform.libc_ver()[0] == "glibc":
2515 from packaging.version import Version
2516
2517 if Version(platform.libc_ver()[1]) < Version("2.37"):
2518 self.skipTest(
2519 "Static linking on ARM64 requires glibc 2.37 where fPIC was enabled (fpic is limited in number of GOT entries)"
2520 )
2521
2522 # First enable examples and static build
2523 return_code, stdout, stderr = run_ns3(
2524 'configure -G "{generator}" --enable-examples --disable-gtk --enable-static'
2525 )
2526
2527 if win32:
2528 # Configuration should fail explaining Windows
2529 # socket libraries cannot be statically linked
2530 self.assertEqual(return_code, 1)
2531 self.assertIn("Static builds are unsupported on Windows", stderr)
2532 else:
2533 # If configuration passes, we are half way done
2534 self.assertEqual(return_code, 0)
2535
2536 # Then try to build one example
2537 return_code, stdout, stderr = run_ns3("build sample-simulator")
2538 self.assertEqual(return_code, 0)
2539 self.assertIn("Built target", stdout)
2540
2541 # Maybe check the built binary for shared library references? Using objdump, otool, etc
2542
2544 """!
2545 Test if we can use python bindings
2546 @return None
2547 """
2548 try:
2549 import cppyy
2550 except ModuleNotFoundError:
2551 self.skipTest("Cppyy was not found")
2552
2553 # First enable examples and static build
2554 return_code, stdout, stderr = run_ns3(
2555 'configure -G "{generator}" --enable-examples --enable-python-bindings'
2556 )
2557
2558 # If configuration passes, we are half way done
2559 self.assertEqual(return_code, 0)
2560
2561 # Then build and run tests
2562 return_code, stdout, stderr = run_program("test.py", "", python=True)
2563 self.assertEqual(return_code, 0)
2564
2565 # Then try to run a specific test
2566 return_code, stdout, stderr = run_program("test.py", "-p mixed-wired-wireless", python=True)
2567 self.assertEqual(return_code, 0)
2568
2569 # Then try to run a specific test with the full relative path
2570 return_code, stdout, stderr = run_program(
2571 "test.py", "-p ./examples/wireless/mixed-wired-wireless", python=True
2572 )
2573 self.assertEqual(return_code, 0)
2574
2576 """!
2577 Test if we had regressions with brite, click and openflow modules
2578 that depend on homonymous libraries
2579 @return None
2580 """
2581 if shutil.which("git") is None:
2582 self.skipTest("Missing git")
2583
2584 if win32:
2585 self.skipTest("Optional components are not supported on Windows")
2586
2587 # First enable automatic components fetching
2588 return_code, stdout, stderr = run_ns3(
2589 "configure --disable-werror -- -DNS3_FETCH_OPTIONAL_COMPONENTS=ON"
2590 )
2591 self.assertEqual(return_code, 0)
2592
2593 # Build the optional components to check if their dependencies were fetched
2594 # and there were no build regressions
2595 return_code, stdout, stderr = run_ns3("build brite click openflow")
2596 self.assertEqual(return_code, 0)
2597
2599 """!
2600 Test if we can link contrib modules to src modules
2601 @return None
2602 """
2603 if shutil.which("git") is None:
2604 self.skipTest("Missing git")
2605
2606 destination_contrib = os.path.join(ns3_path, "contrib/test-contrib-dependency")
2607 destination_src = os.path.join(ns3_path, "src/test-src-dependent-on-contrib")
2608 # Remove pre-existing directories
2609 if os.path.exists(destination_contrib):
2610 shutil.rmtree(destination_contrib)
2611 if os.path.exists(destination_src):
2612 shutil.rmtree(destination_src)
2613
2614 # Always use a fresh copy
2615 shutil.copytree(
2616 os.path.join(ns3_path, "build-support/test-files/test-contrib-dependency"),
2617 destination_contrib,
2618 )
2619 shutil.copytree(
2620 os.path.join(ns3_path, "build-support/test-files/test-src-dependent-on-contrib"),
2621 destination_src,
2622 )
2623
2624 # Then configure
2625 return_code, stdout, stderr = run_ns3("configure --enable-examples")
2626 self.assertEqual(return_code, 0)
2627
2628 # Build the src module that depend on a contrib module
2629 return_code, stdout, stderr = run_ns3("run source-example")
2630 self.assertEqual(return_code, 0)
2631
2632 # Remove module copies
2633 shutil.rmtree(destination_contrib)
2634 shutil.rmtree(destination_src)
2635
2636
2638 """!
2639 Tests ns3 usage in more realistic scenarios
2640 """
2641
2642 def setUp(self):
2643 """!
2644 Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
2645 Here examples, tests and documentation are also enabled.
2646 @return None
2647 """
2648
2649 super().setUp()
2650
2651 # On top of the release build configured by NS3ConfigureTestCase, also enable examples, tests and docs.
2652 return_code, stdout, stderr = run_ns3(
2653 'configure -d release -G "{generator}" --enable-examples --enable-tests'
2654 )
2655 self.config_ok(return_code, stdout, stderr)
2656
2657 # Check if .lock-ns3 exists, then read to get list of executables.
2658 self.assertTrue(os.path.exists(ns3_lock_filename))
2659
2660 ## ns3_executables holds a list of executables in .lock-ns3 # noqa
2662
2663 # Check if .lock-ns3 exists than read to get the list of enabled modules.
2664 self.assertTrue(os.path.exists(ns3_lock_filename))
2665
2666 ## ns3_modules holds a list to the modules enabled stored in .lock-ns3 # noqa
2668
2670 """!
2671 Try to build the project
2672 @return None
2673 """
2674 return_code, stdout, stderr = run_ns3("build")
2675 self.assertEqual(return_code, 0)
2676 self.assertIn("Built target", stdout)
2677 for program in get_programs_list():
2678 self.assertTrue(os.path.exists(program))
2679 libraries = get_libraries_list()
2680 for module in get_enabled_modules():
2681 self.assertIn(module.replace("ns3-", ""), ";".join(libraries))
2682 self.assertIn(cmake_build_project_command, stdout)
2683
2685 """!
2686 Try to build and run test-runner
2687 @return None
2688 """
2689 return_code, stdout, stderr = run_ns3('run "test-runner --list" --verbose')
2690 self.assertEqual(return_code, 0)
2691 self.assertIn("Built target test-runner", stdout)
2692 self.assertIn(cmake_build_target_command(target="test-runner"), stdout)
2693
2695 """!
2696 Try to build and run a library
2697 @return None
2698 """
2699 return_code, stdout, stderr = run_ns3("run core") # this should not work
2700 self.assertEqual(return_code, 1)
2701 self.assertIn("Couldn't find the specified program: core", stderr)
2702
2704 """!
2705 Try to build and run an unknown target
2706 @return None
2707 """
2708 return_code, stdout, stderr = run_ns3("run nonsense") # this should not work
2709 self.assertEqual(return_code, 1)
2710 self.assertIn("Couldn't find the specified program: nonsense", stderr)
2711
2713 """!
2714 Try to run test-runner without building
2715 @return None
2716 """
2717 return_code, stdout, stderr = run_ns3("build test-runner")
2718 self.assertEqual(return_code, 0)
2719
2720 return_code, stdout, stderr = run_ns3('run "test-runner --list" --no-build --verbose')
2721 self.assertEqual(return_code, 0)
2722 self.assertNotIn("Built target test-runner", stdout)
2723 self.assertNotIn(cmake_build_target_command(target="test-runner"), stdout)
2724
2726 """!
2727 Test ns3 fails to run a library
2728 @return None
2729 """
2730 return_code, stdout, stderr = run_ns3("run core --no-build") # this should not work
2731 self.assertEqual(return_code, 1)
2732 self.assertIn("Couldn't find the specified program: core", stderr)
2733
2735 """!
2736 Test ns3 fails to run an unknown program
2737 @return None
2738 """
2739 return_code, stdout, stderr = run_ns3("run nonsense --no-build") # this should not work
2740 self.assertEqual(return_code, 1)
2741 self.assertIn("Couldn't find the specified program: nonsense", stderr)
2742
2744 """!
2745 Test if scratch simulator is executed through gdb and lldb
2746 @return None
2747 """
2748 if shutil.which("gdb") is None:
2749 self.skipTest("Missing gdb")
2750
2751 return_code, stdout, stderr = run_ns3("build scratch-simulator")
2752 self.assertEqual(return_code, 0)
2753
2754 return_code, stdout, stderr = run_ns3(
2755 "run scratch-simulator --gdb --verbose --no-build", env={"gdb_eval": "1"}
2756 )
2757 self.assertEqual(return_code, 0)
2758 self.assertIn("scratch-simulator", stdout)
2759 if win32:
2760 self.assertIn("GNU gdb", stdout)
2761 else:
2762 self.assertIn("No debugging symbols found", stdout)
2763
2765 """!
2766 Test if scratch simulator is executed through valgrind
2767 @return None
2768 """
2769 if shutil.which("valgrind") is None:
2770 self.skipTest("Missing valgrind")
2771
2772 return_code, stdout, stderr = run_ns3("build scratch-simulator")
2773 self.assertEqual(return_code, 0)
2774
2775 return_code, stdout, stderr = run_ns3(
2776 "run scratch-simulator --valgrind --verbose --no-build"
2777 )
2778 self.assertEqual(return_code, 0)
2779 self.assertIn("scratch-simulator", stderr)
2780 self.assertIn("Memcheck", stderr)
2781
2783 """!
2784 Test the doxygen target that does trigger a full build
2785 @return None
2786 """
2787 if shutil.which("doxygen") is None:
2788 self.skipTest("Missing doxygen")
2789
2790 if shutil.which("bash") is None:
2791 self.skipTest("Missing bash")
2792
2793 doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
2794
2795 doxygen_files = ["introspected-command-line.h", "introspected-doxygen.h"]
2796 for filename in doxygen_files:
2797 file_path = os.sep.join([doc_folder, filename])
2798 if os.path.exists(file_path):
2799 os.remove(file_path)
2800
2801 # Rebuilding dot images is super slow, so not removing doxygen products
2802 # doxygen_build_folder = os.sep.join([doc_folder, "html"])
2803 # if os.path.exists(doxygen_build_folder):
2804 # shutil.rmtree(doxygen_build_folder)
2805
2806 return_code, stdout, stderr = run_ns3("docs doxygen")
2807 self.assertEqual(return_code, 0)
2808 self.assertIn(cmake_build_target_command(target="doxygen"), stdout)
2809 self.assertIn("Built target doxygen", stdout)
2810
2812 """!
2813 Test the doxygen target that doesn't trigger a full build
2814 @return None
2815 """
2816 if shutil.which("doxygen") is None:
2817 self.skipTest("Missing doxygen")
2818
2819 # Rebuilding dot images is super slow, so not removing doxygen products
2820 # doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
2821 # doxygen_build_folder = os.sep.join([doc_folder, "html"])
2822 # if os.path.exists(doxygen_build_folder):
2823 # shutil.rmtree(doxygen_build_folder)
2824
2825 return_code, stdout, stderr = run_ns3("docs doxygen-no-build")
2826 self.assertEqual(return_code, 0)
2827 self.assertIn(cmake_build_target_command(target="doxygen-no-build"), stdout)
2828 self.assertIn("Built target doxygen-no-build", stdout)
2829
2831 """!
2832 Test every individual target for Sphinx-based documentation
2833 @return None
2834 """
2835 if shutil.which("sphinx-build") is None:
2836 self.skipTest("Missing sphinx")
2837
2838 doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
2839
2840 # For each sphinx doc target.
2841 for target in ["installation", "contributing", "manual", "models", "tutorial"]:
2842 # First we need to clean old docs, or it will not make any sense.
2843 doc_build_folder = os.sep.join([doc_folder, target, "build"])
2844 doc_temp_folder = os.sep.join([doc_folder, target, "source-temp"])
2845 if os.path.exists(doc_build_folder):
2846 shutil.rmtree(doc_build_folder)
2847 if os.path.exists(doc_temp_folder):
2848 shutil.rmtree(doc_temp_folder)
2849
2850 # Build
2851 return_code, stdout, stderr = run_ns3("docs %s" % target)
2852 self.assertEqual(return_code, 0, target)
2853 self.assertIn(cmake_build_target_command(target="sphinx_%s" % target), stdout)
2854 self.assertIn("Built target sphinx_%s" % target, stdout)
2855
2856 # Check if the docs output folder exists
2857 doc_build_folder = os.sep.join([doc_folder, target, "build"])
2858 self.assertTrue(os.path.exists(doc_build_folder))
2859
2860 # Check if the all the different types are in place (latex, split HTML and single page HTML)
2861 for build_type in ["latex", "html", "singlehtml"]:
2862 self.assertTrue(os.path.exists(os.sep.join([doc_build_folder, build_type])))
2863
2865 """!
2866 Test the documentation target that builds
2867 both doxygen and sphinx based documentation
2868 @return None
2869 """
2870 if shutil.which("doxygen") is None:
2871 self.skipTest("Missing doxygen")
2872 if shutil.which("sphinx-build") is None:
2873 self.skipTest("Missing sphinx")
2874
2875 doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
2876
2877 # First we need to clean old docs, or it will not make any sense.
2878
2879 # Rebuilding dot images is super slow, so not removing doxygen products
2880 # doxygen_build_folder = os.sep.join([doc_folder, "html"])
2881 # if os.path.exists(doxygen_build_folder):
2882 # shutil.rmtree(doxygen_build_folder)
2883
2884 for target in ["manual", "models", "tutorial"]:
2885 doc_build_folder = os.sep.join([doc_folder, target, "build"])
2886 if os.path.exists(doc_build_folder):
2887 shutil.rmtree(doc_build_folder)
2888
2889 return_code, stdout, stderr = run_ns3("docs all")
2890 self.assertEqual(return_code, 0)
2891 self.assertIn(cmake_build_target_command(target="sphinx"), stdout)
2892 self.assertIn("Built target sphinx", stdout)
2893 self.assertIn(cmake_build_target_command(target="doxygen"), stdout)
2894 self.assertIn("Built target doxygen", stdout)
2895
2897 """!
2898 Try to set ownership of scratch-simulator from current user to root,
2899 and change execution permissions
2900 @return None
2901 """
2902
2903 # Test will be skipped if not defined
2904 sudo_password = os.getenv("SUDO_PASSWORD", None)
2905
2906 # Skip test if variable containing sudo password is the default value
2907 if sudo_password is None:
2908 self.skipTest("SUDO_PASSWORD environment variable was not specified")
2909
2910 enable_sudo = read_lock_entry("ENABLE_SUDO")
2911 self.assertFalse(enable_sudo is True)
2912
2913 # First we run to ensure the program was built
2914 return_code, stdout, stderr = run_ns3("run scratch-simulator")
2915 self.assertEqual(return_code, 0)
2916 self.assertIn("Built target scratch_scratch-simulator", stdout)
2917 self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout)
2918 scratch_simulator_path = list(
2919 filter(lambda x: x if "scratch-simulator" in x else None, self.ns3_executables)
2920 )[-1]
2921 prev_fstat = os.stat(scratch_simulator_path) # we get the permissions before enabling sudo
2922
2923 # Now try setting the sudo bits from the run subparser
2924 return_code, stdout, stderr = run_ns3(
2925 "run scratch-simulator --enable-sudo", env={"SUDO_PASSWORD": sudo_password}
2926 )
2927 self.assertEqual(return_code, 0)
2928 self.assertIn("Built target scratch_scratch-simulator", stdout)
2929 self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout)
2930 fstat = os.stat(scratch_simulator_path)
2931
2932 import stat
2933
2934 # If we are on Windows, these permissions mean absolutely nothing,
2935 # and on Fuse builds they might not make any sense, so we need to skip before failing
2936 likely_fuse_mount = (
2937 (prev_fstat.st_mode & stat.S_ISUID) == (fstat.st_mode & stat.S_ISUID)
2938 ) and prev_fstat.st_uid == 0 # noqa
2939
2940 if win32 or likely_fuse_mount:
2941 self.skipTest("Windows or likely a FUSE mount")
2942
2943 # If this is a valid platform, we can continue
2944 self.assertEqual(fstat.st_uid, 0) # check the file was correctly chown'ed by root
2945 self.assertEqual(
2946 fstat.st_mode & stat.S_ISUID, stat.S_ISUID
2947 ) # check if normal users can run as sudo
2948
2949 # Now try setting the sudo bits as a post-build step (as set by configure subparser)
2950 return_code, stdout, stderr = run_ns3("configure --enable-sudo")
2951 self.assertEqual(return_code, 0)
2952
2953 # Check if it was properly set in the lock file
2954 enable_sudo = read_lock_entry("ENABLE_SUDO")
2955 self.assertTrue(enable_sudo)
2956
2957 # Remove old executables
2958 for executable in self.ns3_executables:
2959 if os.path.exists(executable):
2960 os.remove(executable)
2961
2962 # Try to build and then set sudo bits as a post-build step
2963 return_code, stdout, stderr = run_ns3("build", env={"SUDO_PASSWORD": sudo_password})
2964 self.assertEqual(return_code, 0)
2965
2966 # Check if commands are being printed for every target
2967 self.assertIn("chown root", stdout)
2968 self.assertIn("chmod u+s", stdout)
2969 for executable in self.ns3_executables:
2970 self.assertIn(os.path.basename(executable), stdout)
2971
2972 # Check scratch simulator yet again
2973 fstat = os.stat(scratch_simulator_path)
2974 self.assertEqual(fstat.st_uid, 0) # check the file was correctly chown'ed by root
2975 self.assertEqual(
2976 fstat.st_mode & stat.S_ISUID, stat.S_ISUID
2977 ) # check if normal users can run as sudo
2978
2980 """!
2981 Check if command template is working
2982 @return None
2983 """
2984
2985 # Command templates that are empty or do not have a '%s' should fail
2986 return_code0, stdout0, stderr0 = run_ns3("run sample-simulator --command-template")
2987 self.assertEqual(return_code0, 2)
2988 self.assertIn("argument --command-template: expected one argument", stderr0)
2989
2990 return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template=" "')
2991 return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --command-template " "')
2992 return_code3, stdout3, stderr3 = run_ns3('run sample-simulator --command-template "echo "')
2993 self.assertEqual((return_code1, return_code2, return_code3), (1, 1, 1))
2994 for stderr in [stderr1, stderr2, stderr3]:
2995 self.assertIn("not all arguments converted during string formatting", stderr)
2996
2997 # Command templates with %s should at least continue and try to run the target
2998 return_code4, stdout4, _ = run_ns3(
2999 'run sample-simulator --command-template "%s --PrintVersion" --verbose'
3000 )
3001 return_code5, stdout5, _ = run_ns3(
3002 'run sample-simulator --command-template="%s --PrintVersion" --verbose'
3003 )
3004 self.assertEqual((return_code4, return_code5), (0, 0))
3005
3006 self.assertIn("sample-simulator{ext} --PrintVersion".format(ext=ext), stdout4)
3007 self.assertIn("sample-simulator{ext} --PrintVersion".format(ext=ext), stdout5)
3008
3010 """!
3011 Check if all flavors of different argument passing to
3012 executable targets are working
3013 @return None
3014 """
3015
3016 # Test if all argument passing flavors are working
3017 return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help" --verbose')
3018 return_code1, stdout1, stderr1 = run_ns3(
3019 'run sample-simulator --command-template="%s --help" --verbose'
3020 )
3021 return_code2, stdout2, stderr2 = run_ns3("run sample-simulator --verbose -- --help")
3022
3023 self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
3024 self.assertIn("sample-simulator{ext} --help".format(ext=ext), stdout0)
3025 self.assertIn("sample-simulator{ext} --help".format(ext=ext), stdout1)
3026 self.assertIn("sample-simulator{ext} --help".format(ext=ext), stdout2)
3027
3028 # Test if the same thing happens with an additional run argument (e.g. --no-build)
3029 return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help" --no-build')
3030 return_code1, stdout1, stderr1 = run_ns3(
3031 'run sample-simulator --command-template="%s --help" --no-build'
3032 )
3033 return_code2, stdout2, stderr2 = run_ns3("run sample-simulator --no-build -- --help")
3034 self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
3035 self.assertEqual(stdout0, stdout1)
3036 self.assertEqual(stdout1, stdout2)
3037 self.assertEqual(stderr0, stderr1)
3038 self.assertEqual(stderr1, stderr2)
3039
3040 # Now collect results for each argument individually
3041 return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --PrintGlobals" --verbose')
3042 return_code1, stdout1, stderr1 = run_ns3('run "sample-simulator --PrintGroups" --verbose')
3043 return_code2, stdout2, stderr2 = run_ns3('run "sample-simulator --PrintTypeIds" --verbose')
3044
3045 self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
3046 self.assertIn("sample-simulator{ext} --PrintGlobals".format(ext=ext), stdout0)
3047 self.assertIn("sample-simulator{ext} --PrintGroups".format(ext=ext), stdout1)
3048 self.assertIn("sample-simulator{ext} --PrintTypeIds".format(ext=ext), stdout2)
3049
3050 # Then check if all the arguments are correctly merged by checking the outputs
3051 cmd = 'run "sample-simulator --PrintGlobals" --command-template="%s --PrintGroups" --verbose -- --PrintTypeIds'
3052 return_code, stdout, stderr = run_ns3(cmd)
3053 self.assertEqual(return_code, 0)
3054
3055 # The order of the arguments is command template,
3056 # arguments passed with the target itself
3057 # and forwarded arguments after the -- separator
3058 self.assertIn(
3059 "sample-simulator{ext} --PrintGroups --PrintGlobals --PrintTypeIds".format(ext=ext),
3060 stdout,
3061 )
3062
3063 # Check if it complains about the missing -- separator
3064 cmd0 = 'run sample-simulator --command-template="%s " --PrintTypeIds'
3065 cmd1 = "run sample-simulator --PrintTypeIds"
3066
3067 return_code0, stdout0, stderr0 = run_ns3(cmd0)
3068 return_code1, stdout1, stderr1 = run_ns3(cmd1)
3069 self.assertEqual((return_code0, return_code1), (1, 1))
3070 self.assertIn("To forward configuration or runtime options, put them after '--'", stderr0)
3071 self.assertIn("To forward configuration or runtime options, put them after '--'", stderr1)
3072
3074 """!
3075 Test if scratch simulator is executed through lldb
3076 @return None
3077 """
3078 if shutil.which("lldb") is None:
3079 self.skipTest("Missing lldb")
3080
3081 return_code, stdout, stderr = run_ns3("build scratch-simulator")
3082 self.assertEqual(return_code, 0)
3083
3084 return_code, stdout, stderr = run_ns3("run scratch-simulator --lldb --verbose --no-build")
3085 self.assertEqual(return_code, 0)
3086 self.assertIn("scratch-simulator", stdout)
3087 self.assertIn("(lldb) target create", stdout)
3088
3090 """!
3091 Test if CPM and Vcpkg package managers are working properly
3092 @return None
3093 """
3094 # Clean the ns-3 configuration
3095 return_code, stdout, stderr = run_ns3("clean")
3096 self.assertEqual(return_code, 0)
3097
3098 # Cleanup VcPkg leftovers
3099 if os.path.exists("vcpkg"):
3100 shutil.rmtree("vcpkg")
3101
3102 # Copy a test module that consumes armadillo
3103 destination_src = os.path.join(ns3_path, "src/test-package-managers")
3104 # Remove pre-existing directories
3105 if os.path.exists(destination_src):
3106 shutil.rmtree(destination_src)
3107
3108 # Always use a fresh copy
3109 shutil.copytree(
3110 os.path.join(ns3_path, "build-support/test-files/test-package-managers"),
3111 destination_src,
3112 )
3113
3114 with DockerContainerManager(self, "ubuntu:22.04") as container:
3115 # Install toolchain
3116 container.execute("apt-get update")
3117 container.execute("apt-get install -y python3 cmake g++ ninja-build")
3118
3119 # Verify that Armadillo is not available and that we did not
3120 # add any new unnecessary dependency when features are not used
3121 try:
3122 container.execute("./ns3 configure -- -DTEST_PACKAGE_MANAGER:STRING=ON")
3123 self.skipTest("Armadillo is already installed")
3124 except DockerException as e:
3125 pass
3126
3127 # Clean cache to prevent dumb errors
3128 return_code, stdout, stderr = run_ns3("clean")
3129 self.assertEqual(return_code, 0)
3130
3131 # Install CPM and VcPkg shared dependency
3132 container.execute("apt-get install -y git")
3133
3134 # Install Armadillo with CPM
3135 try:
3136 container.execute(
3137 "./ns3 configure -- -DNS3_CPM=ON -DTEST_PACKAGE_MANAGER:STRING=CPM"
3138 )
3139 except DockerException as e:
3140 self.fail()
3141
3142 # Try to build module using CPM's Armadillo
3143 try:
3144 container.execute("./ns3 build test-package-managers")
3145 except DockerException as e:
3146 self.fail()
3147
3148 # Clean cache to prevent dumb errors
3149 return_code, stdout, stderr = run_ns3("clean")
3150 self.assertEqual(return_code, 0)
3151
3152 if arch != "aarch64":
3153 # Install VcPkg dependencies
3154 container.execute("apt-get install -y zip unzip tar curl")
3155
3156 # Install Armadillo dependencies
3157 container.execute("apt-get install -y pkg-config gfortran")
3158
3159 # Install VcPkg
3160 try:
3161 container.execute("./ns3 configure -- -DNS3_VCPKG=ON")
3162 except DockerException as e:
3163 self.fail()
3164
3165 # Install Armadillo with VcPkg
3166 try:
3167 container.execute("./ns3 configure -- -DTEST_PACKAGE_MANAGER:STRING=VCPKG")
3168 except DockerException as e:
3169 self.fail()
3170
3171 # Try to build module using VcPkg's Armadillo
3172 try:
3173 container.execute("./ns3 build test-package-managers")
3174 except DockerException as e:
3175 self.fail()
3176
3177 # Remove test module
3178 if os.path.exists(destination_src):
3179 shutil.rmtree(destination_src)
3180
3182 """!
3183 Test if test.py and command-template handles empty spaces in executable paths correctly
3184 @return None
3185 """
3186 # Clean the ns-3 configuration
3187 return_code, stdout, stderr = run_ns3("clean")
3188 self.assertEqual(return_code, 0)
3189
3190 with DockerContainerManager(self, "ubuntu:22.04") as container:
3191 # Install toolchain
3192 container.execute("apt-get update")
3193 container.execute("apt-get install -y python3 cmake g++ ninja-build")
3194
3195 # Create new copy of ns-3 on a "path with empty spaces"
3196 test_path = "/path with empty spaces/ns-3-dev"
3197 try:
3198 container.execute(f'mkdir -p "{test_path}"')
3199 container.execute(f'cp -R ./ "{test_path}"')
3200 except DockerException as e:
3201 pass
3202
3203 # Configure enabling examples as tests too, filtering to core and sixlowpan
3204 try:
3205 container.execute(
3206 './ns3 configure --enable-examples --enable-tests --filter-module-examples-and-tests="core;sixlowpan"',
3207 workdir=test_path,
3208 )
3209 except DockerException as e:
3210 self.fail()
3211
3212 # Execute tests and examples to see if all work
3213 try:
3214 container.execute("./test.py", workdir=test_path)
3215 except DockerException as e:
3216 self.fail()
3217
3218 # Clean cache to prevent dumb errors
3219 return_code, stdout, stderr = run_ns3("clean")
3220 self.assertEqual(return_code, 0)
3221
3222
3223class NS3QualityControlTestCase(unittest.TestCase):
3224 """!
3225 ns-3 tests to control the quality of the repository over time
3226 """
3227
3229 """!
3230 Check if images in the docs are above a brightness threshold.
3231 This should prevent screenshots with dark UI themes.
3232 @return None
3233 """
3234 if shutil.which("convert") is None:
3235 self.skipTest("Imagemagick was not found")
3236
3237 from pathlib import Path
3238
3239 # Scan for images
3240 image_extensions = ["png", "jpg"]
3241 images = []
3242 for extension in image_extensions:
3243 images += list(Path("./doc").glob("**/figures/*.{ext}".format(ext=extension)))
3244 images += list(Path("./doc").glob("**/figures/**/*.{ext}".format(ext=extension)))
3245
3246 # Get the brightness of an image on a scale of 0-100%
3247 imagemagick_get_image_brightness = 'convert {image} -colorspace HSI -channel b -separate +channel -scale 1x1 -format "%[fx:100*u]" info:'
3248
3249 # We could invert colors of target image to increase its brightness
3250 # convert source.png -channel RGB -negate target.png
3251 brightness_threshold = 50
3252 for image in images:
3253 brightness = subprocess.check_output(
3254 imagemagick_get_image_brightness.format(image=image).split()
3255 )
3256 brightness = float(brightness.decode().strip("'\""))
3257 self.assertGreater(
3258 brightness,
3259 brightness_threshold,
3260 "Image darker than threshold (%d < %d): %s"
3261 % (brightness, brightness_threshold, image),
3262 )
3263
3265 """!
3266 Check if one of the log statements of examples/tests contains/exposes a bug.
3267 @return None
3268 """
3269 # First enable examples and tests with sanitizers
3270 return_code, stdout, stderr = run_ns3(
3271 'configure -G "{generator}" -d release --enable-examples --enable-tests --enable-sanitizers'
3272 )
3273 self.assertEqual(return_code, 0)
3274
3275 # Then build and run tests setting the environment variable
3276 return_code, stdout, stderr = run_program(
3277 "test.py", "", python=True, env={"TEST_LOGS": "1"}
3278 )
3279 self.assertEqual(return_code, 0)
3280
3281
3283 """!
3284 ns-3 complementary tests, allowed to fail, to help control
3285 the quality of the repository over time, by checking the
3286 state of URLs listed and more
3287 """
3288
3290 """!
3291 Test if all urls in source files are alive
3292 @return None
3293 """
3294
3295 # Skip this test if Django is not available
3296 try:
3297 import django
3298 except ImportError:
3299 django = None # noqa
3300 self.skipTest("Django URL validators are not available")
3301
3302 # Skip this test if requests library is not available
3303 try:
3304 import requests
3305 import urllib3
3306
3307 urllib3.disable_warnings()
3308 except ImportError:
3309 requests = None # noqa
3310 self.skipTest("Requests library is not available")
3311
3312 regex = re.compile(r"((http|https)://[^\ \n\‍)\"\'\}><\‍]\;\`\\]*)") # noqa
3313 skipped_files = []
3314
3315 whitelisted_urls = {
3316 "https://gitlab.com/your-user-name/ns-3-dev",
3317 "https://www.nsnam.org/release/ns-allinone-3.31.rc1.tar.bz2",
3318 "https://www.nsnam.org/release/ns-allinone-3.X.rcX.tar.bz2",
3319 "https://www.nsnam.org/releases/ns-3-x",
3320 "https://www.nsnam.org/releases/ns-allinone-3.(x-1",
3321 "https://www.nsnam.org/releases/ns-allinone-3.x.tar.bz2",
3322 "https://ns-buildmaster.ee.washington.edu:8010/",
3323 # split due to command-line formatting
3324 "https://cmake.org/cmake/help/latest/manual/cmake-",
3325 "http://www.ieeeghn.org/wiki/index.php/First-Hand:Digital_Television:_The_",
3326 # Dia placeholder xmlns address
3327 "http://www.lysator.liu.se/~alla/dia/",
3328 # Fails due to bad regex
3329 "http://www.ieeeghn.org/wiki/index.php/First-Hand:Digital_Television:_The_Digital_Terrestrial_Television_Broadcasting_(DTTB",
3330 "http://en.wikipedia.org/wiki/Namespace_(computer_science",
3331 "http://en.wikipedia.org/wiki/Bonobo_(component_model",
3332 "http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85",
3333 "https://github.com/rui314/mold/releases/download/v1.4.2/mold-1.4.2-{arch",
3334 "http://www.nsnam.org/bugzilla/show_bug.cgi?id=",
3335 # historical links
3336 "http://www.research.att.com/info/kpv/",
3337 "http://www.research.att.com/~gsf/",
3338 "http://nsnam.isi.edu/nsnam/index.php/Contributed_Code",
3339 "http://scan5.coverity.com/cgi-bin/upload.py",
3340 # terminal output
3341 "https://github.com/Kitware/CMake/releases/download/v3.27.1/cmake-3.27.1-linux-x86_64.tar.gz-",
3342 "http://mirrors.kernel.org/fedora/releases/11/Everything/i386/os/Packages/",
3343 }
3344
3345 # Scan for all URLs in all files we can parse
3346 files_and_urls = set()
3347 unique_urls = set()
3348 for topdir in ["bindings", "doc", "examples", "src", "utils"]:
3349 for root, dirs, files in os.walk(topdir):
3350 # do not parse files in build directories
3351 if "build" in root or "_static" in root or "source-temp" in root or "html" in root:
3352 continue
3353 for file in files:
3354 filepath = os.path.join(root, file)
3355
3356 # skip everything that isn't a file
3357 if not os.path.isfile(filepath):
3358 continue
3359
3360 # skip svg files
3361 if file.endswith(".svg"):
3362 continue
3363
3364 try:
3365 with open(filepath, "r", encoding="utf-8") as f:
3366 matches = regex.findall(f.read())
3367
3368 # Get first group for each match (containing the URL)
3369 # and strip final punctuation or commas in matched links
3370 # commonly found in the docs
3371 urls = list(
3372 map(lambda x: x[0][:-1] if x[0][-1] in ".," else x[0], matches)
3373 )
3374 except UnicodeDecodeError:
3375 skipped_files.append(filepath)
3376 continue
3377
3378 # Search for new unique URLs and add keep track of their associated source file
3379 for url in set(urls) - unique_urls - whitelisted_urls:
3380 unique_urls.add(url)
3381 files_and_urls.add((filepath, url))
3382
3383 # Instantiate the Django URL validator
3384 from django.core.exceptions import ValidationError # noqa
3385 from django.core.validators import URLValidator # noqa
3386
3387 validate_url = URLValidator()
3388
3389 # User agent string to make ACM and Elsevier let us check if links to papers are working
3390 headers = {
3391 "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"
3392 # noqa
3393 }
3394
3395 def test_file_url(args):
3396 test_filepath, test_url = args
3397 dead_link_msg = None
3398
3399 # Skip invalid URLs
3400 try:
3401 validate_url(test_url)
3402 except ValidationError:
3403 dead_link_msg = "%s: URL %s, invalid URL" % (test_filepath, test_url)
3404 except Exception as e:
3405 self.assertEqual(False, True, msg=e.__str__())
3406
3407 if dead_link_msg is not None:
3408 return dead_link_msg
3409 tries = 3
3410 # Check if valid URLs are alive
3411 while tries > 0:
3412 # Not verifying the certificate (verify=False) is potentially dangerous
3413 # HEAD checks are not as reliable as GET ones,
3414 # in some cases they may return bogus error codes and reasons
3415 try:
3416 response = requests.get(test_url, verify=False, headers=headers, timeout=50)
3417
3418 # In case of success and redirection
3419 if response.status_code in [200, 301]:
3420 dead_link_msg = None
3421 break
3422
3423 # People use the wrong code, but the reason
3424 # can still be correct
3425 if response.status_code in [302, 308, 500, 503]:
3426 if response.reason.lower() in [
3427 "found",
3428 "moved temporarily",
3429 "permanent redirect",
3430 "ok",
3431 "service temporarily unavailable",
3432 ]:
3433 dead_link_msg = None
3434 break
3435 # In case it didn't pass in any of the previous tests,
3436 # set dead_link_msg with the most recent error and try again
3437 dead_link_msg = "%s: URL %s: returned code %d" % (
3438 test_filepath,
3439 test_url,
3440 response.status_code,
3441 )
3442 except requests.exceptions.InvalidURL:
3443 dead_link_msg = "%s: URL %s: invalid URL" % (test_filepath, test_url)
3444 except requests.exceptions.SSLError:
3445 dead_link_msg = "%s: URL %s: SSL error" % (test_filepath, test_url)
3446 except requests.exceptions.TooManyRedirects:
3447 dead_link_msg = "%s: URL %s: too many redirects" % (test_filepath, test_url)
3448 except Exception as e:
3449 try:
3450 error_msg = e.args[0].reason.__str__()
3451 except AttributeError:
3452 error_msg = e.args[0]
3453 dead_link_msg = "%s: URL %s: failed with exception: %s" % (
3454 test_filepath,
3455 test_url,
3456 error_msg,
3457 )
3458 tries -= 1
3459 return dead_link_msg
3460
3461 # Dispatch threads to test multiple URLs concurrently
3462 from concurrent.futures import ThreadPoolExecutor
3463
3464 with ThreadPoolExecutor(max_workers=100) as executor:
3465 dead_links = list(executor.map(test_file_url, list(files_and_urls)))
3466
3467 # Filter out None entries
3468 dead_links = list(sorted(filter(lambda x: x is not None, dead_links)))
3469 self.assertEqual(len(dead_links), 0, msg="\n".join(["Dead links found:", *dead_links]))
3470
3472 """!
3473 Test if all tests can be executed without hitting major memory bugs
3474 @return None
3475 """
3476 return_code, stdout, stderr = run_ns3(
3477 "configure --enable-tests --enable-examples --enable-sanitizers -d optimized"
3478 )
3479 self.assertEqual(return_code, 0)
3480
3481 test_return_code, stdout, stderr = run_program("test.py", "", python=True)
3482 self.assertEqual(test_return_code, 0)
3483
3484
3485def main():
3486 """!
3487 Main function
3488 @return None
3489 """
3490
3491 test_completeness = {
3492 "style": [
3493 NS3UnusedSourcesTestCase,
3494 NS3StyleTestCase,
3495 ],
3496 "build": [
3497 NS3CommonSettingsTestCase,
3498 NS3ConfigureBuildProfileTestCase,
3499 NS3ConfigureTestCase,
3500 NS3BuildBaseTestCase,
3501 NS3ExpectedUseTestCase,
3502 ],
3503 "complete": [
3504 NS3UnusedSourcesTestCase,
3505 NS3StyleTestCase,
3506 NS3CommonSettingsTestCase,
3507 NS3ConfigureBuildProfileTestCase,
3508 NS3ConfigureTestCase,
3509 NS3BuildBaseTestCase,
3510 NS3ExpectedUseTestCase,
3511 NS3QualityControlTestCase,
3512 ],
3513 "extras": [
3514 NS3DependenciesTestCase,
3515 NS3QualityControlThatCanFailTestCase,
3516 ],
3517 }
3518
3519 import argparse
3520
3521 parser = argparse.ArgumentParser("Test suite for the ns-3 buildsystem")
3522 parser.add_argument(
3523 "-c", "--completeness", choices=test_completeness.keys(), default="complete"
3524 )
3525 parser.add_argument("-tn", "--test-name", action="store", default=None, type=str)
3526 parser.add_argument("-rtn", "--resume-from-test-name", action="store", default=None, type=str)
3527 parser.add_argument("-q", "--quiet", action="store_true", default=False)
3528 parser.add_argument("-f", "--failfast", action="store_true", default=False)
3529 args = parser.parse_args(sys.argv[1:])
3530
3531 loader = unittest.TestLoader()
3532 suite = unittest.TestSuite()
3533
3534 # Put tests cases in order
3535 for testCase in test_completeness[args.completeness]:
3536 suite.addTests(loader.loadTestsFromTestCase(testCase))
3537
3538 # Filter tests by name
3539 if args.test_name:
3540 # Generate a dictionary of test names and their objects
3541 tests = dict(map(lambda x: (x._testMethodName, x), suite._tests))
3542
3543 tests_to_run = set(map(lambda x: x if args.test_name in x else None, tests.keys()))
3544 tests_to_remove = set(tests) - set(tests_to_run)
3545 for test_to_remove in tests_to_remove:
3546 suite._tests.remove(tests[test_to_remove])
3547
3548 # Filter tests after a specific name (e.g. to restart from a failing test)
3549 if args.resume_from_test_name:
3550 # Generate a dictionary of test names and their objects
3551 tests = dict(map(lambda x: (x._testMethodName, x), suite._tests))
3552 keys = list(tests.keys())
3553
3554 while args.resume_from_test_name not in keys[0] and len(tests) > 0:
3555 suite._tests.remove(tests[keys[0]])
3556 keys.pop(0)
3557
3558 # Before running, check if ns3rc exists and save it
3559 ns3rc_script_bak = ns3rc_script + ".bak"
3560 if os.path.exists(ns3rc_script) and not os.path.exists(ns3rc_script_bak):
3561 shutil.move(ns3rc_script, ns3rc_script_bak)
3562
3563 # Run tests and fail as fast as possible
3564 runner = unittest.TextTestRunner(failfast=args.failfast, verbosity=1 if args.quiet else 2)
3565 runner.run(suite)
3566
3567 # After completing the tests successfully, restore the ns3rc file
3568 if os.path.exists(ns3rc_script_bak):
3569 shutil.move(ns3rc_script_bak, ns3rc_script)
3570
3571
3572if __name__ == "__main__":
3573 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:2064
test_08_InstallationAndUninstallation(self)
Tries setting a ns3 version, then installing it.
Definition test-ns3.py:2259
test_12_CppyyBindings(self)
Test if we can use python bindings.
Definition test-ns3.py:2543
test_13_FetchOptionalComponents(self)
Test if we had regressions with brite, click and openflow modules that depend on homonymous libraries...
Definition test-ns3.py:2575
test_07_OutputDirectory(self)
Try setting a different output directory and if everything is in the right place and still working co...
Definition test-ns3.py:2183
test_02_BuildNonExistingTargets(self)
Try building core-test library without tests enabled.
Definition test-ns3.py:2087
test_01_BuildExistingTargets(self)
Try building the core library.
Definition test-ns3.py:2078
setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned.
Definition test-ns3.py:2069
test_04_BuildProjectNoTaskLines(self)
Try hiding task lines.
Definition test-ns3.py:2109
test_11_StaticBuilds(self)
Test if we can build a static ns-3 library and link it to static programs.
Definition test-ns3.py:2508
test_06_TestVersionFile(self)
Test if changing the version file affects the library names.
Definition test-ns3.py:2139
test_14_LinkContribModuleToSrcModule(self)
Test if we can link contrib modules to src modules.
Definition test-ns3.py:2598
test_03_BuildProject(self)
Try building the project:
Definition test-ns3.py:2097
test_09_Scratches(self)
Tries to build scratch-simulator and subdir/scratch-simulator-subdir.
Definition test-ns3.py:2426
test_05_BreakBuild(self)
Try removing an essential file to break the build.
Definition test-ns3.py:2118
test_10_AmbiguityCheck(self)
Test if ns3 can alert correctly in case a shortcut collision happens.
Definition test-ns3.py:2454
ns3_libraries
ns3_libraries holds a list of built module libraries # noqa
Definition test-ns3.py:2076
ns3 tests related to generic options
Definition test-ns3.py: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:1496
test_25_CheckBareConfig(self)
Check for regressions in a bare ns-3 configuration.
Definition test-ns3.py:2042
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:1920
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:1664
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:1746
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:2027
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:1861
test_23_PrecompiledHeaders(self)
Check if precompiled headers are being enabled correctly.
Definition test-ns3.py:2008
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:1599
test_17_CMakePerformanceTracing(self)
Test if CMake performance tracing works and produces the cmake_performance_trace.log file.
Definition test-ns3.py:1643
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:1794
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:2637
test_12_SphinxDocumentation(self)
Test every individual target for Sphinx-based documentation.
Definition test-ns3.py:2830
test_03_BuildAndRunExistingLibraryTarget(self)
Try to build and run a library.
Definition test-ns3.py:2694
test_01_BuildProject(self)
Try to build the project.
Definition test-ns3.py:2669
test_08_RunNoBuildGdb(self)
Test if scratch simulator is executed through gdb and lldb.
Definition test-ns3.py:2743
test_11_DoxygenWithoutBuild(self)
Test the doxygen target that doesn't trigger a full build.
Definition test-ns3.py:2811
test_06_RunNoBuildExistingLibraryTarget(self)
Test ns3 fails to run a library.
Definition test-ns3.py:2725
test_14_EnableSudo(self)
Try to set ownership of scratch-simulator from current user to root, and change execution permissions...
Definition test-ns3.py:2896
setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned Here examples,...
Definition test-ns3.py:2642
test_02_BuildAndRunExistingExecutableTarget(self)
Try to build and run test-runner.
Definition test-ns3.py:2684
test_18_CpmAndVcpkgManagers(self)
Test if CPM and Vcpkg package managers are working properly.
Definition test-ns3.py:3089
test_15_CommandTemplate(self)
Check if command template is working.
Definition test-ns3.py:2979
test_07_RunNoBuildNonExistingExecutableTarget(self)
Test ns3 fails to run an unknown program.
Definition test-ns3.py:2734
test_05_RunNoBuildExistingExecutableTarget(self)
Try to run test-runner without building.
Definition test-ns3.py:2712
test_16_ForwardArgumentsToRunTargets(self)
Check if all flavors of different argument passing to executable targets are working.
Definition test-ns3.py:3009
test_17_RunNoBuildLldb(self)
Test if scratch simulator is executed through lldb.
Definition test-ns3.py:3073
test_19_EmptySpaceHandlingOnTestAndCommandTemplate(self)
Test if test.py and command-template handles empty spaces in executable paths correctly.
Definition test-ns3.py:3181
test_09_RunNoBuildValgrind(self)
Test if scratch simulator is executed through valgrind.
Definition test-ns3.py:2764
test_04_BuildAndRunNonExistingTarget(self)
Try to build and run an unknown target.
Definition test-ns3.py:2703
test_13_Documentation(self)
Test the documentation target that builds both doxygen and sphinx based documentation.
Definition test-ns3.py:2864
test_10_DoxygenWithBuild(self)
Test the doxygen target that does trigger a full build.
Definition test-ns3.py:2782
ns-3 tests to control the quality of the repository over time
Definition test-ns3.py:3223
test_02_CheckForBrokenLogs(self)
Check if one of the log statements of examples/tests contains/exposes a bug.
Definition test-ns3.py:3264
test_01_CheckImageBrightness(self)
Check if images in the docs are above a brightness threshold.
Definition test-ns3.py:3228
ns-3 complementary tests, allowed to fail, to help control the quality of the repository over time,...
Definition test-ns3.py:3282
test_01_CheckForDeadLinksInSources(self)
Test if all urls in source files are alive.
Definition test-ns3.py:3289
test_02_MemoryCheckWithSanitizers(self)
Test if all tests can be executed without hitting major memory bugs.
Definition test-ns3.py:3471
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