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