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