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