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