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