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