A Discrete-Event Network Simulator
API
test-ns3.py
Go to the documentation of this file.
1#! /usr/bin/env python3
2# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
3#
4# Copyright (c) 2021 Universidade de Brasília
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License version 2 as
8# published by the Free Software Foundation;
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19# Author: Gabriel Ferreira <gabrielcarvfer@gmail.com>
20
21"""!
22Test suite for the ns3 wrapper script
23"""
24
25import glob
26import os
27import re
28import shutil
29import subprocess
30import sys
31import unittest
32from functools import partial
33
34# Get path containing ns3
35ns3_path = os.path.dirname(os.path.abspath(os.sep.join([__file__, "../../"])))
36ns3_lock_filename = os.path.join(ns3_path, ".lock-ns3_%s_build" % sys.platform)
37ns3_script = os.sep.join([ns3_path, "ns3"])
38ns3rc_script = os.sep.join([ns3_path, ".ns3rc"])
39usual_outdir = os.sep.join([ns3_path, "build"])
40usual_lib_outdir = os.sep.join([usual_outdir, "lib"])
41
42# Move the current working directory to the ns-3-dev folder
43os.chdir(ns3_path)
44
45# Cmake commands
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=max(1, os.cpu_count() - 1)
49 )
50
51
52def run_ns3(args, env=None):
53 """!
54 Runs the ns3 wrapper script with arguments
55 @param args: string containing arguments that will get split before calling ns3
56 @param env: environment variables dictionary
57 @return tuple containing (error code, stdout and stderr)
58 """
59 if "clean" in args:
60 possible_leftovers = ["contrib/borked", "contrib/calibre"]
61 for leftover in possible_leftovers:
62 if os.path.exists(leftover):
63 shutil.rmtree(leftover, ignore_errors=True)
64 return run_program(ns3_script, args, python=True, env=env)
65
66
67# Adapted from https://github.com/metabrainz/picard/blob/master/picard/util/__init__.py
68def run_program(program, args, python=False, cwd=ns3_path, env=None):
69 """!
70 Runs a program with the given arguments and returns a tuple containing (error code, stdout and stderr)
71 @param program: program to execute (or python script)
72 @param args: string containing arguments that will get split before calling the program
73 @param python: flag indicating whether the program is a python script
74 @param cwd: the working directory used that will be the root folder for the execution
75 @param env: environment variables dictionary
76 @return tuple containing (error code, stdout and stderr)
77 """
78 if type(args) != str:
79 raise Exception("args should be a string")
80
81 # Include python interpreter if running a python script
82 if python:
83 arguments = [sys.executable, program]
84 else:
85 arguments = [program]
86
87 if args != "":
88 arguments.extend(re.findall("(?:\".*?\"|\S)+", args))
89
90 for i in range(len(arguments)):
91 arguments[i] = arguments[i].replace("\"", "")
92
93 # Forward environment variables used by the ns3 script
94 current_env = os.environ.copy()
95
96 # Add different environment variables
97 if env:
98 current_env.update(env)
99
100 # Call program with arguments
101 ret = subprocess.run(
102 arguments,
103 stdin=subprocess.DEVNULL,
104 stdout=subprocess.PIPE,
105 stderr=subprocess.PIPE,
106 cwd=cwd, # run process from the ns-3-dev path
107 env=current_env
108 )
109 # Return (error code, stdout and stderr)
110 return ret.returncode, ret.stdout.decode(sys.stdout.encoding), ret.stderr.decode(sys.stderr.encoding)
111
112
114 """!
115 Extracts the programs list from .lock-ns3
116 @return list of programs.
117 """
118 values = {}
119 with open(ns3_lock_filename) as f:
120 exec(f.read(), globals(), values)
121 return values["ns3_runnable_programs"]
122
123
124def get_libraries_list(lib_outdir=usual_lib_outdir):
125 """!
126 Gets a list of built libraries
127 @param lib_outdir: path containing libraries
128 @return list of built libraries.
129 """
130 return glob.glob(lib_outdir + '/*', recursive=True)
131
132
133def get_headers_list(outdir=usual_outdir):
134 """!
135 Gets a list of header files
136 @param outdir: path containing headers
137 @return list of headers.
138 """
139 return glob.glob(outdir + '/**/*.h', recursive=True)
140
141
143 """!
144 Read interesting entries from the .lock-ns3 file
145 @param entry: entry to read from .lock-ns3
146 @return value of the requested entry.
147 """
148 values = {}
149 with open(ns3_lock_filename) as f:
150 exec(f.read(), globals(), values)
151 return values.get(entry, None)
152
153
155 """!
156 Check if tests are enabled in the .lock-ns3
157 @return bool.
158 """
159 return read_lock_entry("ENABLE_TESTS")
160
161
163 """
164 Check if tests are enabled in the .lock-ns3
165 @return list of enabled modules (prefixed with 'ns3-').
166 """
167 return read_lock_entry("NS3_ENABLED_MODULES")
168
169
170class NS3UnusedSourcesTestCase(unittest.TestCase):
171 """!
172 ns-3 tests related to checking if source files were left behind, not being used by CMake
173 """
174
175
176 directory_and_files = {}
177
178 def setUp(self):
179 """!
180 Scan all C++ source files and add them to a list based on their path
181 @return None
182 """
183 for root, dirs, files in os.walk(ns3_path):
184 if "gitlab-ci-local" in root:
185 continue
186 for name in files:
187 if name.endswith(".cc"):
188 path = os.path.join(root, name)
189 directory = os.path.dirname(path)
190 if directory not in self.directory_and_files:
191 self.directory_and_files[directory] = []
192 self.directory_and_files[directory].append(path)
193
195 """!
196 Test if all example source files are being used in their respective CMakeLists.txt
197 @return None
198 """
199 unused_sources = set()
200 for example_directory in self.directory_and_files.keys():
201 # Skip non-example directories
202 if os.sep + "examples" not in example_directory:
203 continue
204
205 # Open the examples CMakeLists.txt and read it
206 with open(os.path.join(example_directory, "CMakeLists.txt"), "r") as f:
207 cmake_contents = f.read()
208
209 # For each file, check if it is in the CMake contents
210 for file in self.directory_and_files[example_directory]:
211 # We remove the .cc because some examples sources can be written as ${example_name}.cc
212 if os.path.basename(file).replace(".cc", "") not in cmake_contents:
213 unused_sources.add(file)
214
215 self.assertListEqual([], list(unused_sources))
216
218 """!
219 Test if all module source files are being used in their respective CMakeLists.txt
220 @return None
221 """
222 unused_sources = set()
223 for directory in self.directory_and_files.keys():
224 # Skip examples and bindings directories
225 is_not_module = not ("src" in directory or "contrib" in directory)
226 is_example = os.sep + "examples" in directory
227 is_bindings = os.sep + "bindings" in directory
228
229 if is_not_module or is_bindings or is_example:
230 continue
231
232 # We can be in one of the module subdirectories (helper, model, test, bindings, etc)
233 # Navigate upwards until we hit a CMakeLists.txt
234 cmake_path = os.path.join(directory, "CMakeLists.txt")
235 while not os.path.exists(cmake_path):
236 parent_directory = os.path.dirname(os.path.dirname(cmake_path))
237 cmake_path = os.path.join(parent_directory, os.path.basename(cmake_path))
238
239 # Open the module CMakeLists.txt and read it
240 with open(cmake_path, "r") as f:
241 cmake_contents = f.read()
242
243 # For each file, check if it is in the CMake contents
244 for file in self.directory_and_files[directory]:
245 if os.path.basename(file) not in cmake_contents:
246 unused_sources.add(file)
247
248 # Remove temporary exceptions
249 exceptions = ["win32-system-wall-clock-ms.cc", # Should be removed with MR784
250 ]
251 for exception in exceptions:
252 for unused_source in unused_sources:
253 if os.path.basename(unused_source) == exception:
254 unused_sources.remove(unused_source)
255 break
256
257 self.assertListEqual([], list(unused_sources))
258
260 """!
261 Test if all utils source files are being used in their respective CMakeLists.txt
262 @return None
263 """
264 unused_sources = set()
265 for directory in self.directory_and_files.keys():
266 # Skip directories that are not utils
267 is_module = "src" in directory or "contrib" in directory
268 if os.sep + "utils" not in directory or is_module:
269 continue
270
271 # We can be in one of the module subdirectories (helper, model, test, bindings, etc)
272 # Navigate upwards until we hit a CMakeLists.txt
273 cmake_path = os.path.join(directory, "CMakeLists.txt")
274 while not os.path.exists(cmake_path):
275 parent_directory = os.path.dirname(os.path.dirname(cmake_path))
276 cmake_path = os.path.join(parent_directory, os.path.basename(cmake_path))
277
278 # Open the module CMakeLists.txt and read it
279 with open(cmake_path, "r") as f:
280 cmake_contents = f.read()
281
282 # For each file, check if it is in the CMake contents
283 for file in self.directory_and_files[directory]:
284 if os.path.basename(file) not in cmake_contents:
285 unused_sources.add(file)
286
287 self.assertListEqual([], list(unused_sources))
288
289
290class NS3CommonSettingsTestCase(unittest.TestCase):
291 """!
292 ns3 tests related to generic options
293 """
294
295 def setUp(self):
296 """!
297 Clean configuration/build artifacts before common commands
298 @return None
299 """
300 super().setUp()
301 # No special setup for common test cases other than making sure we are working on a clean directory.
302 run_ns3("clean")
303
305 """!
306 Test not passing any arguments to
307 @return None
308 """
309 return_code, stdout, stderr = run_ns3("")
310 self.assertEqual(return_code, 1)
311 self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
312
314 """!
315 Test only passing --quiet argument to ns3
316 @return None
317 """
318 return_code, stdout, stderr = run_ns3("--quiet")
319 self.assertEqual(return_code, 1)
320 self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
321
323 """!
324 Test only passing 'show config' argument to ns3
325 @return None
326 """
327 return_code, stdout, stderr = run_ns3("show config")
328 self.assertEqual(return_code, 1)
329 self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
330
332 """!
333 Test only passing 'show profile' argument to ns3
334 @return None
335 """
336 return_code, stdout, stderr = run_ns3("show profile")
337 self.assertEqual(return_code, 1)
338 self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
339
341 """!
342 Test only passing 'show version' argument to ns3
343 @return None
344 """
345 return_code, stdout, stderr = run_ns3("show version")
346 self.assertEqual(return_code, 1)
347 self.assertIn("You need to configure ns-3 first: try ./ns3 configure", stdout)
348
349
350class NS3ConfigureBuildProfileTestCase(unittest.TestCase):
351 """!
352 ns3 tests related to build profiles
353 """
354
355 def setUp(self):
356 """!
357 Clean configuration/build artifacts before testing configuration settings
358 @return None
359 """
360 super().setUp()
361 # No special setup for common test cases other than making sure we are working on a clean directory.
362 run_ns3("clean")
363
364 def test_01_Debug(self):
365 """!
366 Test the debug build
367 @return None
368 """
369 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d debug --enable-verbose")
370 self.assertEqual(return_code, 0)
371 self.assertIn("Build profile : debug", stdout)
372 self.assertIn("Build files have been written to", stdout)
373
374 # Build core to check if profile suffixes match the expected.
375 return_code, stdout, stderr = run_ns3("build core")
376 self.assertEqual(return_code, 0)
377 self.assertIn("Built target libcore", stdout)
378
379 libraries = get_libraries_list()
380 self.assertGreater(len(libraries), 0)
381 self.assertIn("core-debug", libraries[0])
382
384 """!
385 Test the release build
386 @return None
387 """
388 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d release")
389 self.assertEqual(return_code, 0)
390 self.assertIn("Build profile : release", stdout)
391 self.assertIn("Build files have been written to", stdout)
392
394 """!
395 Test the optimized build
396 @return None
397 """
398 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d optimized --enable-verbose")
399 self.assertEqual(return_code, 0)
400 self.assertIn("Build profile : optimized", stdout)
401 self.assertIn("Build files have been written to", stdout)
402
403 # Build core to check if profile suffixes match the expected
404 return_code, stdout, stderr = run_ns3("build core")
405 self.assertEqual(return_code, 0)
406 self.assertIn("Built target libcore", stdout)
407
408 libraries = get_libraries_list()
409 self.assertGreater(len(libraries), 0)
410 self.assertIn("core-optimized", libraries[0])
411
412 def test_04_Typo(self):
413 """!
414 Test a build type with a typo
415 @return None
416 """
417 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d Optimized")
418 self.assertEqual(return_code, 2)
419 self.assertIn("invalid choice: 'Optimized'", stderr)
420
421 def test_05_TYPO(self):
422 """!
423 Test a build type with another typo
424 @return None
425 """
426 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d OPTIMIZED")
427 self.assertEqual(return_code, 2)
428 self.assertIn("invalid choice: 'OPTIMIZED'", stderr)
429
430
431class NS3BaseTestCase(unittest.TestCase):
432 """!
433 Generic test case with basic function inherited by more complex tests.
434 """
435
436
437 cleaned_once = False
438
439 def config_ok(self, return_code, stdout):
440 """!
441 Check if configuration for release mode worked normally
442 @param return_code: return code from CMake
443 @param stdout: output from CMake.
444 @return None
445 """
446 self.assertEqual(return_code, 0)
447 self.assertIn("Build profile : release", stdout)
448 self.assertIn("Build files have been written to", stdout)
449
450 def setUp(self):
451 """!
452 Clean configuration/build artifacts before testing configuration and build settings
453 After configuring the build as release,
454 check if configuration worked and check expected output files.
455 @return None
456 """
457 super().setUp()
458
459 if os.path.exists(ns3rc_script):
460 os.remove(ns3rc_script)
461
462 # We only clear it once and then update the settings by changing flags or consuming ns3rc.
463 if not NS3BaseTestCase.cleaned_once:
464 NS3BaseTestCase.cleaned_once = True
465 run_ns3("clean")
466 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -d release --enable-verbose")
467 self.config_ok(return_code, stdout)
468
469 # Check if .lock-ns3 exists, then read to get list of executables.
470 self.assertTrue(os.path.exists(ns3_lock_filename))
471
473
474 # Check if .lock-ns3 exists than read to get the list of enabled modules.
475 self.assertTrue(os.path.exists(ns3_lock_filename))
476
478
479
481 """!
482 Test ns3 configuration options
483 """
484
485
486 cleaned_once = False
487
488 def setUp(self):
489 """!
490 Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
491 @return None
492 """
493 if not NS3ConfigureTestCase.cleaned_once:
494 NS3ConfigureTestCase.cleaned_once = True
495 NS3BaseTestCase.cleaned_once = False
496 super().setUp()
497
499 """!
500 Test enabling and disabling examples
501 @return None
502 """
503 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples")
504
505 # This just tests if we didn't break anything, not that we actually have enabled anything.
506 self.config_ok(return_code, stdout)
507
508 # If nothing went wrong, we should have more executables in the list after enabling the examples.
509 self.assertGreater(len(get_programs_list()), len(self.ns3_executables))
510
511 # Now we disabled them back.
512 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-examples")
513
514 # This just tests if we didn't break anything, not that we actually have enabled anything.
515 self.config_ok(return_code, stdout)
516
517 # Then check if they went back to the original list.
518 self.assertEqual(len(get_programs_list()), len(self.ns3_executables))
519
520 def test_02_Tests(self):
521 """!
522 Test enabling and disabling tests
523 @return None
524 """
525 # Try enabling tests
526 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-tests")
527 self.config_ok(return_code, stdout)
528
529 # Then try building the libcore test
530 return_code, stdout, stderr = run_ns3("build core-test")
531
532 # If nothing went wrong, this should have worked
533 self.assertEqual(return_code, 0)
534 self.assertIn("Built target libcore-test", stdout)
535
536 # Now we disabled the tests
537 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-tests")
538 self.config_ok(return_code, stdout)
539
540 # Now building the library test should fail
541 return_code, stdout, stderr = run_ns3("build core-test")
542
543 # Then check if they went back to the original list
544 self.assertEqual(return_code, 1)
545 self.assertIn("Target to build does not exist: core-test", stdout)
546
548 """!
549 Test enabling specific modules
550 @return None
551 """
552 # Try filtering enabled modules to network+Wi-Fi and their dependencies
553 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-modules='network;wifi'")
554 self.config_ok(return_code, stdout)
555
556 # At this point we should have fewer modules
557 enabled_modules = get_enabled_modules()
558 self.assertLess(len(get_enabled_modules()), len(self.ns3_modules))
559 self.assertIn("ns3-network", enabled_modules)
560 self.assertIn("ns3-wifi", enabled_modules)
561
562 # Try enabling only core
563 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-modules='core' --enable-python-bindings")
564 self.config_ok(return_code, stdout)
565 self.assertIn("ns3-core", get_enabled_modules())
566
567 # Try cleaning the list of enabled modules to reset to the normal configuration.
568 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-modules='' --disable-python-bindings")
569 self.config_ok(return_code, stdout)
570
571 # At this point we should have the same amount of modules that we had when we started.
572 self.assertEqual(len(get_enabled_modules()), len(self.ns3_modules))
573
575 """!
576 Test disabling specific modules
577 @return None
578 """
579 # Try filtering disabled modules to disable lte and modules that depend on it.
580 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-modules='lte;wimax'")
581 self.config_ok(return_code, stdout)
582
583 # At this point we should have fewer modules.
584 enabled_modules = get_enabled_modules()
585 self.assertLess(len(enabled_modules), len(self.ns3_modules))
586 self.assertNotIn("ns3-lte", enabled_modules)
587 self.assertNotIn("ns3-wimax", enabled_modules)
588
589 # Try cleaning the list of enabled modules to reset to the normal configuration.
590 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-modules=''")
591 self.config_ok(return_code, stdout)
592
593 # At this point we should have the same amount of modules that we had when we started.
594 self.assertEqual(len(get_enabled_modules()), len(self.ns3_modules))
595
597 """!
598 Test enabling comma-separated (waf-style) examples
599 @return None
600 """
601 # Try filtering enabled modules to network+Wi-Fi and their dependencies.
602 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-modules='network,wifi'")
603 self.config_ok(return_code, stdout)
604
605 # At this point we should have fewer modules.
606 enabled_modules = get_enabled_modules()
607 self.assertLess(len(get_enabled_modules()), len(self.ns3_modules))
608 self.assertIn("ns3-network", enabled_modules)
609 self.assertIn("ns3-wifi", enabled_modules)
610
611 # Try cleaning the list of enabled modules to reset to the normal configuration.
612 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-modules=''")
613 self.config_ok(return_code, stdout)
614
615 # At this point we should have the same amount of modules that we had when we started.
616 self.assertEqual(len(get_enabled_modules()), len(self.ns3_modules))
617
619 """!
620 Test disabling comma-separated (waf-style) examples
621 @return None
622 """
623 # Try filtering disabled modules to disable lte and modules that depend on it.
624 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-modules='lte,mpi'")
625 self.config_ok(return_code, stdout)
626
627 # At this point we should have fewer modules.
628 enabled_modules = get_enabled_modules()
629 self.assertLess(len(enabled_modules), len(self.ns3_modules))
630 self.assertNotIn("ns3-lte", enabled_modules)
631 self.assertNotIn("ns3-mpi", enabled_modules)
632
633 # Try cleaning the list of enabled modules to reset to the normal configuration.
634 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-modules=''")
635 self.config_ok(return_code, stdout)
636
637 # At this point we should have the same amount of modules that we had when we started.
638 self.assertEqual(len(get_enabled_modules()), len(self.ns3_modules))
639
640 def test_07_Ns3rc(self):
641 """!
642 Test loading settings from the ns3rc config file
643 @return None
644 """
645 ns3rc_template = "# ! /usr/bin/env python\
646 \
647 # A list of the modules that will be enabled when ns-3 is run.\
648 # Modules that depend on the listed modules will be enabled also.\
649 #\
650 # All modules can be enabled by choosing 'all_modules'.\
651 modules_enabled = [{modules}]\
652 \
653 # Set this equal to true if you want examples to be run.\
654 examples_enabled = {examples}\
655 \
656 # Set this equal to true if you want tests to be run.\
657 tests_enabled = {tests}\
658 "
659
660 # Now we repeat the command line tests but with the ns3rc file.
661 with open(ns3rc_script, "w") as f:
662 f.write(ns3rc_template.format(modules="'lte'", examples="False", tests="True"))
663
664 # Reconfigure.
665 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
666 self.config_ok(return_code, stdout)
667
668 # Check.
669 enabled_modules = get_enabled_modules()
670 self.assertLess(len(get_enabled_modules()), len(self.ns3_modules))
671 self.assertIn("ns3-lte", enabled_modules)
672 self.assertTrue(get_test_enabled())
673 self.assertEqual(len(get_programs_list()), len(self.ns3_executables))
674
675 # Replace the ns3rc file
676 with open(ns3rc_script, "w") as f:
677 f.write(ns3rc_template.format(modules="'wifi'", examples="True", tests="False"))
678
679 # Reconfigure
680 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
681 self.config_ok(return_code, stdout)
682
683 # Check
684 enabled_modules = get_enabled_modules()
685 self.assertLess(len(get_enabled_modules()), len(self.ns3_modules))
686 self.assertIn("ns3-wifi", enabled_modules)
687 self.assertFalse(get_test_enabled())
688 self.assertGreater(len(get_programs_list()), len(self.ns3_executables))
689
690 # Then we roll back by removing the ns3rc config file
691 os.remove(ns3rc_script)
692
693 # Reconfigure
694 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
695 self.config_ok(return_code, stdout)
696
697 # Check
698 self.assertEqual(len(get_enabled_modules()), len(self.ns3_modules))
699 self.assertFalse(get_test_enabled())
700 self.assertEqual(len(get_programs_list()), len(self.ns3_executables))
701
702 def test_08_DryRun(self):
703 """!
704 Test dry-run (printing commands to be executed instead of running them)
705 @return None
706 """
707 run_ns3("clean")
708
709 # Try dry-run before and after the positional commands (outputs should match)
710 for positional_command in ["configure", "build", "clean"]:
711 return_code, stdout, stderr = run_ns3("--dry-run %s" % positional_command)
712 return_code1, stdout1, stderr1 = run_ns3("%s --dry-run" % positional_command)
713
714 self.assertEqual(return_code, return_code1)
715 self.assertEqual(stdout, stdout1)
716 self.assertEqual(stderr, stderr1)
717
718 run_ns3("clean")
719
720 # Build target before using below
721 run_ns3("configure -G \"Unix Makefiles\" -d release --enable-verbose")
722 run_ns3("build scratch-simulator")
723
724 # Run all cases and then check outputs
725 return_code0, stdout0, stderr0 = run_ns3("--dry-run run scratch-simulator")
726 return_code1, stdout1, stderr1 = run_ns3("run scratch-simulator")
727 return_code2, stdout2, stderr2 = run_ns3("--dry-run run scratch-simulator --no-build")
728 return_code3, stdout3, stderr3 = run_ns3("run scratch-simulator --no-build")
729
730 # Return code and stderr should be the same for all of them.
731 self.assertEqual(sum([return_code0, return_code1, return_code2, return_code3]), 0)
732 self.assertEqual([stderr0, stderr1, stderr2, stderr3], [""] * 4)
733
734 scratch_path = None
735 for program in get_programs_list():
736 if "scratch-simulator" in program and "subdir" not in program:
737 scratch_path = program
738 break
739
740 # Scratches currently have a 'scratch_' prefix in their CMake targets
741 # Case 0: dry-run + run (should print commands to build target and then run)
742 self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout0)
743 self.assertIn(scratch_path, stdout0)
744
745 # Case 1: run (should print only make build message)
746 self.assertNotIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout1)
747 self.assertIn("Built target", stdout1)
748 self.assertNotIn(scratch_path, stdout1)
749
750 # Case 2: dry-run + run-no-build (should print commands to run the target)
751 self.assertIn("The following commands would be executed:", stdout2)
752 self.assertIn(scratch_path, stdout2)
753
754 # Case 3: run-no-build (should print the target output only)
755 self.assertNotIn("Finished executing the following commands:", stdout3)
756 self.assertNotIn(scratch_path, stdout3)
757
759 """!
760 Test if ns3 is propagating back the return code from the executables called with the run command
761 @return None
762 """
763 # From this point forward we are reconfiguring in debug mode
764 return_code, _, _ = run_ns3("clean")
765 self.assertEqual(return_code, 0)
766
767 return_code, _, _ = run_ns3("configure -G \"Unix Makefiles\" --enable-examples --enable-tests")
768 self.assertEqual(return_code, 0)
769
770 # Build necessary executables
771 return_code, stdout, stderr = run_ns3("build command-line-example test-runner")
772 self.assertEqual(return_code, 0)
773
774 # Now some tests will succeed normally
775 return_code, stdout, stderr = run_ns3("run \"test-runner --test-name=command-line\" --no-build")
776 self.assertEqual(return_code, 0)
777
778 # Now some tests will fail during NS_COMMANDLINE_INTROSPECTION
779 return_code, stdout, stderr = run_ns3("run \"test-runner --test-name=command-line\" --no-build",
780 env={"NS_COMMANDLINE_INTROSPECTION": ".."}
781 )
782 self.assertNotEqual(return_code, 0)
783
784 # Cause a sigsegv
785 sigsegv_example = os.path.join(ns3_path, "scratch", "sigsegv.cc")
786 with open(sigsegv_example, "w") as f:
787 f.write("""
788 int main (int argc, char *argv[])
789 {
790 char *s = "hello world"; *s = 'H';
791 return 0;
792 }
793 """)
794 return_code, stdout, stderr = run_ns3("run sigsegv")
795 self.assertEqual(return_code, 245)
796 self.assertIn("sigsegv-default' died with <Signals.SIGSEGV: 11>", stdout)
797
798 # Cause an abort
799 abort_example = os.path.join(ns3_path, "scratch", "abort.cc")
800 with open(abort_example, "w") as f:
801 f.write("""
802 #include "ns3/core-module.h"
803
804 using namespace ns3;
805 int main (int argc, char *argv[])
806 {
807 NS_ABORT_IF(true);
808 return 0;
809 }
810 """)
811 return_code, stdout, stderr = run_ns3("run abort")
812 self.assertEqual(return_code, 250)
813 self.assertIn("abort-default' died with <Signals.SIGABRT: 6>", stdout)
814
815 os.remove(sigsegv_example)
816 os.remove(abort_example)
817
819 """!
820 Test passing 'show config' argument to ns3 to get the configuration table
821 @return None
822 """
823 return_code, stdout, stderr = run_ns3("show config")
824 self.assertEqual(return_code, 0)
825 self.assertIn("Summary of optional NS-3 features", stdout)
826
828 """!
829 Test passing 'show profile' argument to ns3 to get the build profile
830 @return None
831 """
832 return_code, stdout, stderr = run_ns3("show profile")
833 self.assertEqual(return_code, 0)
834 self.assertIn("Build profile: default", stdout)
835
837 """!
838 Test passing 'show version' argument to ns3 to get the build version
839 @return None
840 """
841 return_code, _, _ = run_ns3("configure -G \"Unix Makefiles\" --enable-build-version")
842 self.assertEqual(return_code, 0)
843
844 return_code, stdout, stderr = run_ns3("show version")
845 self.assertEqual(return_code, 0)
846 self.assertIn("ns-3 version:", stdout)
847
849 """!
850 Test if CMake target names for scratches and ns3 shortcuts
851 are working correctly
852 @return None
853 """
854
855 test_files = ["scratch/main.cc",
856 "scratch/empty.cc",
857 "scratch/subdir1/main.cc",
858 "scratch/subdir2/main.cc"]
859
860 # Create test scratch files
861 for path in test_files:
862 filepath = os.path.join(ns3_path, path)
863 os.makedirs(os.path.dirname(filepath), exist_ok=True)
864 with open(filepath, "w") as f:
865 if "main" in path:
866 f.write("int main (int argc, char *argv[]){}")
867 else:
868 # no main function will prevent this target from
869 # being created, we should skip it and continue
870 # processing without crashing
871 f.write("")
872
873 # Reload the cmake cache to pick them up
874 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
875 self.assertEqual(return_code, 0)
876
877 # Try to build them with ns3 and cmake
878 for path in test_files:
879 path = path.replace(".cc", "")
880 return_code1, stdout1, stderr1 = run_program("cmake", "--build . --target %s"
881 % path.replace("/", "_"),
882 cwd=os.path.join(ns3_path, "cmake-cache"))
883 return_code2, stdout2, stderr2 = run_ns3("build %s" % path)
884 if "main" in path:
885 self.assertEqual(return_code1, 0)
886 self.assertEqual(return_code2, 0)
887 else:
888 self.assertEqual(return_code1, 2)
889 self.assertEqual(return_code2, 1)
890
891 # Try to run them
892 for path in test_files:
893 path = path.replace(".cc", "")
894 return_code, stdout, stderr = run_ns3("run %s --no-build" % path)
895 if "main" in path:
896 self.assertEqual(return_code, 0)
897 else:
898 self.assertEqual(return_code, 1)
899
900 # Delete the test files and reconfigure to clean them up
901 for path in test_files:
902 source_absolute_path = os.path.join(ns3_path, path)
903 os.remove(source_absolute_path)
904 if "empty" in path:
905 continue
906 filename = os.path.basename(path).replace(".cc", "")
907 executable_absolute_path = os.path.dirname(os.path.join(ns3_path, "build", path))
908 executable_name = list(filter(lambda x: filename in x,
909 os.listdir(executable_absolute_path)
910 )
911 )[0]
912
913 os.remove(os.path.join(executable_absolute_path, executable_name))
914 if path not in ["scratch/main.cc", "scratch/empty.cc"]:
915 os.rmdir(os.path.dirname(source_absolute_path))
916
917 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
918 self.assertEqual(return_code, 0)
919
921 """!
922 Test if ns3 is inserting additional arguments by MPICH and OpenMPI to run on the CI
923 @return None
924 """
925 # Skip test if mpi is not installed
926 if shutil.which("mpiexec") is None:
927 return
928
929 # Ensure sample simulator was built
930 return_code, stdout, stderr = run_ns3("build sample-simulator")
931 self.assertEqual(return_code, 0)
932
933 # Get executable path
934 sample_simulator_path = list(filter(lambda x: "sample-simulator" in x, self.ns3_executables))[0]
935
936 mpi_command = "--dry-run run sample-simulator --command-template=\"mpiexec -np 2 %s\""
937 non_mpi_command = "--dry-run run sample-simulator --command-template=\"echo %s\""
938
939 # Get the commands to run sample-simulator in two processes with mpi
940 return_code, stdout, stderr = run_ns3(mpi_command)
941 self.assertEqual(return_code, 0)
942 self.assertIn("mpiexec -np 2 %s" % sample_simulator_path, stdout)
943
944 # Get the commands to run sample-simulator in two processes with mpi, now with the environment variable
945 return_code, stdout, stderr = run_ns3(mpi_command, env={"MPI_CI": "1"})
946 self.assertEqual(return_code, 0)
947 if shutil.which("ompi_info"):
948 self.assertIn("mpiexec --allow-run-as-root --oversubscribe -np 2 %s" % sample_simulator_path, stdout)
949 else:
950 self.assertIn("mpiexec --allow-run-as-root -np 2 %s" % sample_simulator_path, stdout)
951
952 # Now we repeat for the non-mpi command
953 return_code, stdout, stderr = run_ns3(non_mpi_command)
954 self.assertEqual(return_code, 0)
955 self.assertIn("echo %s" % sample_simulator_path, stdout)
956
957 # Again the non-mpi command, with the MPI_CI environment variable set
958 return_code, stdout, stderr = run_ns3(non_mpi_command, env={"MPI_CI": "1"})
959 self.assertEqual(return_code, 0)
960 self.assertIn("echo %s" % sample_simulator_path, stdout)
961
963 """!
964 Test if CMake and ns3 fail in the expected ways when:
965 - examples from modules or general examples fail if they depend on a
966 library with a name shorter than 4 characters or are disabled when
967 a library is non-existant
968 - a module library passes the configuration but fails to build due to
969 a missing library
970 @return None
971 """
972 os.makedirs("contrib/borked", exist_ok=True)
973 os.makedirs("contrib/borked/examples", exist_ok=True)
974
975 # Test if configuration succeeds and building the module library fails
976 with open("contrib/borked/examples/CMakeLists.txt", "w") as f:
977 f.write("")
978 for invalid_or_non_existant_library in ["", "gsd", "lib", "libfi", "calibre"]:
979 with open("contrib/borked/CMakeLists.txt", "w") as f:
980 f.write("""
981 build_lib(
982 LIBNAME borked
983 SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
984 LIBRARIES_TO_LINK ${libcore} %s
985 )
986 """ % invalid_or_non_existant_library)
987
988 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples")
989 if invalid_or_non_existant_library in ["", "gsd", "libfi", "calibre"]:
990 self.assertEqual(return_code, 0)
991 elif invalid_or_non_existant_library in ["lib"]:
992 self.assertEqual(return_code, 1)
993 self.assertIn("Invalid library name: %s" % invalid_or_non_existant_library, stderr)
994 else:
995 pass
996
997 return_code, stdout, stderr = run_ns3("build borked")
998 if invalid_or_non_existant_library in [""]:
999 self.assertEqual(return_code, 0)
1000 elif invalid_or_non_existant_library in ["lib"]:
1001 self.assertEqual(return_code, 2) # should fail due to invalid library name
1002 self.assertIn("Invalid library name: %s" % invalid_or_non_existant_library, stderr)
1003 elif invalid_or_non_existant_library in ["gsd", "libfi", "calibre"]:
1004 self.assertEqual(return_code, 2) # should fail due to missing library
1005 self.assertIn("cannot find -l%s" % invalid_or_non_existant_library, stderr)
1006 else:
1007 pass
1008
1009 # Now test if the example can be built with:
1010 # - no additional library (should work)
1011 # - invalid library names (should fail to configure)
1012 # - valid library names but inexistent libraries (should not create a target)
1013 with open("contrib/borked/CMakeLists.txt", "w") as f:
1014 f.write("""
1015 build_lib(
1016 LIBNAME borked
1017 SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1018 LIBRARIES_TO_LINK ${libcore}
1019 )
1020 """)
1021 for invalid_or_non_existant_library in ["", "gsd", "lib", "libfi", "calibre"]:
1022 with open("contrib/borked/examples/CMakeLists.txt", "w") as f:
1023 f.write("""
1024 build_lib_example(
1025 NAME borked-example
1026 SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty-main.cc
1027 LIBRARIES_TO_LINK ${libborked} %s
1028 )
1029 """ % invalid_or_non_existant_library)
1030
1031 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
1032 if invalid_or_non_existant_library in ["", "gsd", "libfi", "calibre"]:
1033 self.assertEqual(return_code, 0) # should be able to configure
1034 elif invalid_or_non_existant_library in ["lib"]:
1035 self.assertEqual(return_code, 1) # should fail to even configure
1036 self.assertIn("Invalid library name: %s" % invalid_or_non_existant_library, stderr)
1037 else:
1038 pass
1039
1040 return_code, stdout, stderr = run_ns3("build borked-example")
1041 if invalid_or_non_existant_library in [""]:
1042 self.assertEqual(return_code, 0) # should be able to build
1043 elif invalid_or_non_existant_library in ["libf"]:
1044 self.assertEqual(return_code, 2) # should fail due to missing configuration
1045 self.assertIn("Invalid library name: %s" % invalid_or_non_existant_library, stderr)
1046 elif invalid_or_non_existant_library in ["gsd", "libfi", "calibre"]:
1047 self.assertEqual(return_code, 1) # should fail to find target
1048 self.assertIn("Target to build does not exist: borked-example", stdout)
1049 else:
1050 pass
1051
1052 shutil.rmtree("contrib/borked", ignore_errors=True)
1053
1055 """!
1056 Test if CMake can properly handle modules containing "lib",
1057 which is used internally as a prefix for module libraries
1058 @return None
1059 """
1060
1061 os.makedirs("contrib/calibre", exist_ok=True)
1062 os.makedirs("contrib/calibre/examples", exist_ok=True)
1063
1064 # Now test if we can have a library with "lib" in it
1065 with open("contrib/calibre/examples/CMakeLists.txt", "w") as f:
1066 f.write("")
1067 with open("contrib/calibre/CMakeLists.txt", "w") as f:
1068 f.write("""
1069 build_lib(
1070 LIBNAME calibre
1071 SOURCE_FILES ${PROJECT_SOURCE_DIR}/build-support/empty.cc
1072 LIBRARIES_TO_LINK ${libcore}
1073 )
1074 """)
1075
1076 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
1077
1078 # This only checks if configuration passes
1079 self.assertEqual(return_code, 0)
1080
1081 # This checks if the contrib modules were printed correctly
1082 self.assertIn("calibre", stdout)
1083
1084 # This checks not only if "lib" from "calibre" was incorrectly removed,
1085 # but also if the pkgconfig file was generated with the correct name
1086 self.assertNotIn("care", stdout)
1087 self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake-cache", "pkgconfig", "ns3-calibre.pc")))
1088
1089 # Check if we can build this library
1090 return_code, stdout, stderr = run_ns3("build calibre")
1091 self.assertEqual(return_code, 0)
1092 self.assertIn(cmake_build_target_command(target="libcalibre"), stdout)
1093
1094 shutil.rmtree("contrib/calibre", ignore_errors=True)
1095
1097 """!
1098 Test if CMake performance tracing works and produces the
1099 cmake_performance_trace.log file
1100 @return None
1101 """
1102 return_code, stdout, stderr = run_ns3("configure --trace-performance")
1103 self.assertEqual(return_code, 0)
1104 self.assertIn("--profiling-format=google-trace --profiling-output=../cmake_performance_trace.log", stdout)
1105 self.assertTrue(os.path.exists(os.path.join(ns3_path, "cmake_performance_trace.log")))
1106
1107
1109 """!
1110 Tests ns3 regarding building the project
1111 """
1112
1113
1114 cleaned_once = False
1115
1116 def setUp(self):
1117 """!
1118 Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
1119 @return None
1120 """
1121 if not NS3BuildBaseTestCase.cleaned_once:
1122 NS3BuildBaseTestCase.cleaned_once = True
1123 NS3BaseTestCase.cleaned_once = False
1124 super().setUp()
1125
1127
1129 """!
1130 Try building the core library
1131 @return None
1132 """
1133 return_code, stdout, stderr = run_ns3("build core")
1134 self.assertEqual(return_code, 0)
1135 self.assertIn("Built target libcore", stdout)
1136
1138 """!
1139 Try building core-test library without tests enabled
1140 @return None
1141 """
1142 # tests are not enabled, so the target isn't available
1143 return_code, stdout, stderr = run_ns3("build core-test")
1144 self.assertEqual(return_code, 1)
1145 self.assertIn("Target to build does not exist: core-test", stdout)
1146
1148 """!
1149 Try building the project:
1150 @return None
1151 """
1152 return_code, stdout, stderr = run_ns3("build")
1153 self.assertEqual(return_code, 0)
1154 self.assertIn("Built target", stdout)
1155 for program in get_programs_list():
1156 self.assertTrue(os.path.exists(program))
1157 self.assertIn(cmake_build_project_command, stdout)
1158
1160 """!
1161 Try hiding task lines
1162 @return None
1163 """
1164 return_code, stdout, stderr = run_ns3("--quiet build")
1165 self.assertEqual(return_code, 0)
1166 self.assertIn(cmake_build_project_command, stdout)
1167
1169 """!
1170 Try removing an essential file to break the build
1171 @return None
1172 """
1173 # change an essential file to break the build.
1174 attribute_cc_path = os.sep.join([ns3_path, "src", "core", "model", "attribute.cc"])
1175 attribute_cc_bak_path = attribute_cc_path + ".bak"
1176 shutil.move(attribute_cc_path, attribute_cc_bak_path)
1177
1178 # build should break.
1179 return_code, stdout, stderr = run_ns3("build")
1180 self.assertNotEqual(return_code, 0)
1181
1182 # move file back.
1183 shutil.move(attribute_cc_bak_path, attribute_cc_path)
1184
1185 # build should work again.
1186 return_code, stdout, stderr = run_ns3("build")
1187 self.assertEqual(return_code, 0)
1188
1190 """!
1191 Test if changing the version file affects the library names
1192 @return None
1193 """
1194 version_file = os.sep.join([ns3_path, "VERSION"])
1195 with open(version_file, "w") as f:
1196 f.write("3-00\n")
1197
1198 # Reconfigure.
1199 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
1200 self.config_ok(return_code, stdout)
1201
1202 # Build.
1203 return_code, stdout, stderr = run_ns3("build")
1204 self.assertEqual(return_code, 0)
1205 self.assertIn("Built target", stdout)
1206
1207 # Programs with new versions.
1208 new_programs = get_programs_list()
1209
1210 # Check if they exist.
1211 for program in new_programs:
1212 self.assertTrue(os.path.exists(program))
1213
1214 # Check if we still have the same number of binaries.
1215 self.assertEqual(len(new_programs), len(self.ns3_executablesns3_executables))
1216
1217 # Check if versions changed from 3-dev to 3-00.
1218 libraries = get_libraries_list()
1219 new_libraries = list(set(libraries).difference(set(self.ns3_libraries)))
1220 self.assertEqual(len(new_libraries), len(self.ns3_libraries))
1221 for library in new_libraries:
1222 self.assertNotIn("libns3-dev", library)
1223 self.assertIn("libns3-00", library)
1224 self.assertTrue(os.path.exists(library))
1225
1226 # Restore version file.
1227 with open(version_file, "w") as f:
1228 f.write("3-dev\n")
1229
1230 # Reset flag to let it clean the build.
1231 NS3BuildBaseTestCase.cleaned_once = False
1232
1234 """!
1235 Try setting a different output directory and if everything is
1236 in the right place and still working correctly
1237 @return None
1238 """
1239 # Re-build to return to the original state.
1240 run_ns3("build")
1241
1242
1244
1245
1247
1248 # Delete built programs and libraries to check if they were restored later.
1249 for program in self.ns3_executablesns3_executables:
1250 os.remove(program)
1251 for library in self.ns3_libraries:
1252 os.remove(library)
1253
1254 # Reconfigure setting the output folder to ns-3-dev/build/release (both as an absolute path or relative).
1255 absolute_path = os.sep.join([ns3_path, "build", "release"])
1256 relative_path = os.sep.join(["build", "release"])
1257 for different_out_dir in [absolute_path, relative_path]:
1258 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --out=%s" % different_out_dir)
1259 self.config_ok(return_code, stdout)
1260 self.assertIn("Build directory : %s" % absolute_path, stdout)
1261
1262 # Build
1263 run_ns3("build")
1264
1265 # Check if we have the same number of binaries and that they were built correctly.
1266 new_programs = get_programs_list()
1267 self.assertEqual(len(new_programs), len(self.ns3_executablesns3_executables))
1268 for program in new_programs:
1269 self.assertTrue(os.path.exists(program))
1270
1271 # Check if we have the same number of libraries and that they were built correctly.
1272 libraries = get_libraries_list(os.sep.join([absolute_path, "lib"]))
1273 new_libraries = list(set(libraries).difference(set(self.ns3_libraries)))
1274 self.assertEqual(len(new_libraries), len(self.ns3_libraries))
1275 for library in new_libraries:
1276 self.assertTrue(os.path.exists(library))
1277
1278 # Remove files in the different output dir.
1279 shutil.rmtree(absolute_path)
1280
1281 # Restore original output directory.
1282 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --out=''")
1283 self.config_ok(return_code, stdout)
1284 self.assertIn("Build directory : %s" % usual_outdir, stdout)
1285
1286 # Try re-building.
1287 run_ns3("build")
1288
1289 # Check if we have the same binaries we had at the beginning.
1290 new_programs = get_programs_list()
1291 self.assertEqual(len(new_programs), len(self.ns3_executablesns3_executables))
1292 for program in new_programs:
1293 self.assertTrue(os.path.exists(program))
1294
1295 # Check if we have the same libraries we had at the beginning.
1296 libraries = get_libraries_list()
1297 self.assertEqual(len(libraries), len(self.ns3_libraries))
1298 for library in libraries:
1299 self.assertTrue(os.path.exists(library))
1300
1302 """!
1303 Tries setting a ns3 version, then installing it.
1304 After that, tries searching for ns-3 with CMake's find_package(ns3).
1305 Finally, tries using core library in a 3rd-party project
1306 @return None
1307 """
1308 # Remove existing libraries from the previous step.
1309 libraries = get_libraries_list()
1310 for library in libraries:
1311 os.remove(library)
1312
1313 # 3-dev version format is not supported by CMake, so we use 3.01.
1314 version_file = os.sep.join([ns3_path, "VERSION"])
1315 with open(version_file, "w") as f:
1316 f.write("3-01\n")
1317
1318 # Reconfigure setting the installation folder to ns-3-dev/build/install.
1319 install_prefix = os.sep.join([ns3_path, "build", "install"])
1320 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --prefix=%s" % install_prefix)
1321 self.config_ok(return_code, stdout)
1322
1323 # Build.
1324 run_ns3("build")
1325 libraries = get_libraries_list()
1326 headers = get_headers_list()
1327
1328 # Install.
1329 run_ns3("install")
1330
1331 # Find out if libraries were installed to lib or lib64 (Fedora thing).
1332 lib64 = os.path.exists(os.sep.join([install_prefix, "lib64"]))
1333 installed_libdir = os.sep.join([install_prefix, ("lib64" if lib64 else "lib")])
1334
1335 # Make sure all libraries were installed.
1336 installed_libraries = get_libraries_list(installed_libdir)
1337 installed_libraries_list = ";".join(installed_libraries)
1338 for library in libraries:
1339 library_name = os.path.basename(library)
1340 self.assertIn(library_name, installed_libraries_list)
1341
1342 # Make sure all headers were installed.
1343 installed_headers = get_headers_list(install_prefix)
1344 missing_headers = list(set([os.path.basename(x) for x in headers])
1345 - (set([os.path.basename(x) for x in installed_headers]))
1346 )
1347 self.assertEqual(len(missing_headers), 0)
1348
1349 # Now create a test CMake project and try to find_package ns-3.
1350 test_main_file = os.sep.join([install_prefix, "main.cpp"])
1351 with open(test_main_file, "w") as f:
1352 f.write("""
1353 #include <ns3/core-module.h>
1354 using namespace ns3;
1355 int main ()
1356 {
1357 Simulator::Stop (Seconds (1.0));
1358 Simulator::Run ();
1359 Simulator::Destroy ();
1360 return 0;
1361 }
1362 """)
1363
1364 # We try to use this library without specifying a version,
1365 # specifying ns3-01 (text version with 'dev' is not supported)
1366 # and specifying ns3-00 (a wrong version)
1367 for version in ["", "3.01", "3.00"]:
1368 find_package_import = """
1369 list(APPEND CMAKE_PREFIX_PATH ./{lib}/cmake/ns3)
1370 find_package(ns3 {version} COMPONENTS libcore)
1371 target_link_libraries(test PRIVATE ns3::libcore)
1372 """.format(lib=("lib64" if lib64 else "lib"), version=version)
1373 pkgconfig_import = """
1374 list(APPEND CMAKE_PREFIX_PATH ./)
1375 include(FindPkgConfig)
1376 pkg_check_modules(ns3 REQUIRED IMPORTED_TARGET ns3-core{version})
1377 target_link_libraries(test PUBLIC PkgConfig::ns3)
1378 """.format(lib=("lib64" if lib64 else "lib"),
1379 version="=" + version if version else ""
1380 )
1381
1382 for import_type in [pkgconfig_import, find_package_import]:
1383 test_cmake_project = """
1384 cmake_minimum_required(VERSION 3.10..3.10)
1385 project(ns3_consumer CXX)
1386 add_executable(test main.cpp)
1387 """ + import_type
1388
1389 test_cmake_project_file = os.sep.join([install_prefix, "CMakeLists.txt"])
1390 with open(test_cmake_project_file, "w") as f:
1391 f.write(test_cmake_project)
1392
1393 # Configure the test project
1394 cmake = shutil.which("cmake")
1395 return_code, stdout, stderr = run_program(cmake,
1396 "-DCMAKE_BUILD_TYPE=debug .",
1397 cwd=install_prefix)
1398 if version == "3.00":
1399 self.assertEqual(return_code, 1)
1400 if import_type == find_package_import:
1401 self.assertIn('Could not find a configuration file for package "ns3" that is compatible',
1402 stderr.replace("\n", ""))
1403 elif import_type == pkgconfig_import:
1404 self.assertIn('A required package was not found',
1405 stderr.replace("\n", ""))
1406 else:
1407 raise Exception("Unknown import type")
1408 else:
1409 self.assertEqual(return_code, 0)
1410 self.assertIn("Build files", stdout)
1411
1412 # Build the test project making use of import ns-3
1413 return_code, stdout, stderr = run_program("cmake", "--build .", cwd=install_prefix)
1414
1415 if version == "3.00":
1416 self.assertEqual(return_code, 2)
1417 self.assertGreater(len(stderr), 0)
1418 else:
1419 self.assertEqual(return_code, 0)
1420 self.assertIn("Built target", stdout)
1421
1422 # Try running the test program that imports ns-3
1423 return_code, stdout, stderr = run_program("./test", "", cwd=install_prefix)
1424 self.assertEqual(return_code, 0)
1425
1426 # Uninstall
1427 return_code, stdout, stderr = run_ns3("uninstall")
1428 self.assertIn("Built target uninstall", stdout)
1429
1430 # Restore 3-dev version file
1431 with open(version_file, "w") as f:
1432 f.write("3-dev\n")
1433
1434 # Reset flag to let it clean the build
1435 NS3BuildBaseTestCase.cleaned_once = False
1436
1438 """!
1439 Tries to build scratch-simulator and subdir/scratch-simulator-subdir
1440 @return None
1441 """
1442 # Build.
1443 targets = {"scratch/scratch-simulator": "scratch-simulator",
1444 "scratch/scratch-simulator.cc": "scratch-simulator",
1445 "scratch-simulator": "scratch-simulator",
1446 "scratch/subdir/scratch-simulator-subdir": "subdir_scratch-simulator-subdir",
1447 "subdir/scratch-simulator-subdir": "subdir_scratch-simulator-subdir",
1448 "scratch-simulator-subdir": "subdir_scratch-simulator-subdir",
1449 }
1450 for (target_to_run, target_cmake) in targets.items():
1451 # Test if build is working.
1452 build_line = "target scratch_%s" % target_cmake
1453 return_code, stdout, stderr = run_ns3("build %s" % target_to_run)
1454 self.assertEqual(return_code, 0)
1455 self.assertIn(build_line, stdout)
1456
1457 # Test if run is working
1458 return_code, stdout, stderr = run_ns3("run %s --verbose" % target_to_run)
1459 self.assertEqual(return_code, 0)
1460 self.assertIn(build_line, stdout)
1461 stdout = stdout.replace("scratch_%s" % target_cmake, "") # remove build lines
1462 self.assertIn(target_to_run.split("/")[-1].replace(".cc", ""), stdout)
1463
1464 NS3BuildBaseTestCase.cleaned_once = False
1465
1467 """!
1468 Test if cmake is calling pybindgen through modulegen to generate
1469 the bindings source files correctly
1470 @return None
1471 """
1472
1473 # Skip this test if pybindgen is not available
1474 try:
1475 import pybindgen
1476 except Exception:
1477 self.skipTest("Pybindgen is not available")
1478
1479 # Check if the number of runnable python scripts is equal to 0
1480 python_scripts = read_lock_entry("ns3_runnable_scripts")
1481 self.assertEqual(len(python_scripts), 0)
1482
1483 # First we enable python bindings
1484 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples --enable-tests --enable-python-bindings")
1485 self.assertEqual(return_code, 0)
1486
1487 # Then look for python bindings sources
1488 core_bindings_generated_sources_path = os.path.join(ns3_path, "build", "src", "core", "bindings")
1489 core_bindings_sources_path = os.path.join(ns3_path, "src", "core", "bindings")
1490 core_bindings_path = os.path.join(ns3_path, "build", "bindings", "python", "ns")
1491 core_bindings_header = os.path.join(core_bindings_generated_sources_path, "ns3module.h")
1492 core_bindings_source = os.path.join(core_bindings_generated_sources_path, "ns3module.cc")
1493 self.assertTrue(os.path.exists(core_bindings_header))
1494 self.assertTrue(os.path.exists(core_bindings_source))
1495
1496 # Then try to build the bindings for the core module
1497 return_code, stdout, stderr = run_ns3("build core-bindings")
1498 self.assertEqual(return_code, 0)
1499
1500 # Then check if it was built
1501 self.assertGreater(len(list(filter(lambda x: "_core" in x, os.listdir(core_bindings_path)))), 0)
1502
1503 # Now enable python bindings scanning
1504 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" -- -DNS3_SCAN_PYTHON_BINDINGS=ON")
1505 self.assertEqual(return_code, 0)
1506
1507 # Get the file status for the current scanned bindings
1508 bindings_sources = os.listdir(core_bindings_sources_path)
1509 bindings_sources = [os.path.join(core_bindings_sources_path, y) for y in bindings_sources]
1510 timestamps = {}
1511 [timestamps.update({x: os.stat(x)}) for x in bindings_sources]
1512
1513 # Try to scan the bindings for the core module
1514 return_code, stdout, stderr = run_ns3("build core-apiscan")
1515 self.assertEqual(return_code, 0)
1516
1517 # Check if they exist, are not empty and have a different timestamp
1518 generated_python_files = ["callbacks_list.py", "modulegen__gcc_LP64.py"]
1519 for binding_file in timestamps.keys():
1520 if os.path.basename(binding_file) in generated_python_files:
1521 self.assertTrue(os.path.exists(binding_file))
1522 self.assertGreater(os.stat(binding_file).st_size, 0)
1523 new_fstat = os.stat(binding_file)
1524 self.assertNotEqual(timestamps[binding_file].st_mtime, new_fstat.st_mtime)
1525
1526 # Then delete the old bindings sources
1527 for f in os.listdir(core_bindings_generated_sources_path):
1528 os.remove(os.path.join(core_bindings_generated_sources_path, f))
1529
1530 # Reconfigure to recreate the source files
1531 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\"")
1532 self.assertEqual(return_code, 0)
1533
1534 # Check again if they exist
1535 self.assertTrue(os.path.exists(core_bindings_header))
1536 self.assertTrue(os.path.exists(core_bindings_source))
1537
1538 # Build the core bindings again
1539 return_code, stdout, stderr = run_ns3("build core-bindings")
1540 self.assertEqual(return_code, 0)
1541
1542 # Then check if it was built
1543 self.assertGreater(len(list(filter(lambda x: "_core" in x, os.listdir(core_bindings_path)))), 0)
1544
1545 # We are on python anyway, so we can just try to load it from here
1546 sys.path.insert(0, os.path.join(core_bindings_path, ".."))
1547 try:
1548 from ns import core
1549 except ImportError:
1550 self.assertTrue(True)
1551
1552 # Check if ns3 can find and run the python example
1553 return_code, stdout, stderr = run_ns3("run sample-simulator.py")
1554 self.assertEqual(return_code, 0)
1555
1556 # Check if test.py can find and run the python example
1557 return_code, stdout, stderr = run_program("./test.py", "-p src/core/examples/sample-simulator.py", python=True)
1558 self.assertEqual(return_code, 0)
1559
1560 # Since python examples do not require recompilation,
1561 # test if we still can run the python examples after disabling examples
1562 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --disable-examples")
1563 self.assertEqual(return_code, 0)
1564
1565 # Check if the lock file has python runnable python scripts (it should)
1566 python_scripts = read_lock_entry("ns3_runnable_scripts")
1567 self.assertGreater(len(python_scripts), 0)
1568
1569 # Try to run an example
1570 return_code, stdout, stderr = run_ns3("run sample-simulator.py")
1571 self.assertEqual(return_code, 0)
1572
1573 NS3BuildBaseTestCase.cleaned_once = False
1574
1576 """!
1577 Test if ns3 can alert correctly in case a shortcut collision happens
1578 @return None
1579 """
1580
1581 # First enable examples
1582 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples")
1583 self.assertEqual(return_code, 0)
1584
1585 # Copy second.cc from the tutorial examples to the scratch folder
1586 shutil.copy("./examples/tutorial/second.cc", "./scratch/second.cc")
1587
1588 # Reconfigure to re-scan the scratches
1589 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples")
1590 self.assertEqual(return_code, 0)
1591
1592 # Try to run second and collide
1593 return_code, stdout, stderr = run_ns3("build second")
1594 self.assertEqual(return_code, 1)
1595 self.assertIn(
1596 'Build target "second" is ambiguous. Try one of these: "scratch/second", "examples/tutorial/second"',
1597 stdout
1598 )
1599
1600 # Try to run scratch/second and succeed
1601 return_code, stdout, stderr = run_ns3("build scratch/second")
1602 self.assertEqual(return_code, 0)
1603 self.assertIn(cmake_build_target_command(target="scratch_second"), stdout)
1604
1605 # Try to run scratch/second and succeed
1606 return_code, stdout, stderr = run_ns3("build tutorial/second")
1607 self.assertEqual(return_code, 0)
1608 self.assertIn(cmake_build_target_command(target="second"), stdout)
1609
1610 # Try to run second and collide
1611 return_code, stdout, stderr = run_ns3("run second")
1612 self.assertEqual(return_code, 1)
1613 self.assertIn(
1614 'Run target "second" is ambiguous. Try one of these: "scratch/second", "examples/tutorial/second"',
1615 stdout
1616 )
1617
1618 # Try to run scratch/second and succeed
1619 return_code, stdout, stderr = run_ns3("run scratch/second")
1620 self.assertEqual(return_code, 0)
1621
1622 # Try to run scratch/second and succeed
1623 return_code, stdout, stderr = run_ns3("run tutorial/second")
1624 self.assertEqual(return_code, 0)
1625
1626 # Remove second
1627 os.remove("./scratch/second.cc")
1628
1629
1631 """!
1632 Tests ns3 usage in more realistic scenarios
1633 """
1634
1635
1636 cleaned_once = False
1637
1638 def setUp(self):
1639 """!
1640 Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned
1641 Here examples, tests and documentation are also enabled.
1642 @return None
1643 """
1644 if not NS3ExpectedUseTestCase.cleaned_once:
1645 NS3ExpectedUseTestCase.cleaned_once = True
1646 NS3BaseTestCase.cleaned_once = False
1647 super().setUp()
1648
1649 # On top of the release build configured by NS3ConfigureTestCase, also enable examples, tests and docs.
1650 return_code, stdout, stderr = run_ns3("configure -G \"Unix Makefiles\" --enable-examples --enable-tests")
1651 self.config_ok(return_code, stdout)
1652
1653 # Check if .lock-ns3 exists, then read to get list of executables.
1654 self.assertTrue(os.path.exists(ns3_lock_filename))
1655
1656
1658
1659 # Check if .lock-ns3 exists than read to get the list of enabled modules.
1660 self.assertTrue(os.path.exists(ns3_lock_filename))
1661
1662
1664
1666 """!
1667 Try to build the project
1668 @return None
1669 """
1670 return_code, stdout, stderr = run_ns3("build")
1671 self.assertEqual(return_code, 0)
1672 self.assertIn("Built target", stdout)
1673 for program in get_programs_list():
1674 self.assertTrue(os.path.exists(program))
1675 libraries = get_libraries_list()
1676 for module in get_enabled_modules():
1677 self.assertIn(module.replace("ns3-", ""), ";".join(libraries))
1678 self.assertIn(cmake_build_project_command, stdout)
1679
1681 """!
1682 Try to build and run test-runner
1683 @return None
1684 """
1685 return_code, stdout, stderr = run_ns3('run "test-runner --list" --verbose')
1686 self.assertEqual(return_code, 0)
1687 self.assertIn("Built target test-runner", stdout)
1688 self.assertIn(cmake_build_target_command(target="test-runner"), stdout)
1689
1691 """!
1692 Try to build and run a library
1693 @return None
1694 """
1695 return_code, stdout, stderr = run_ns3("run core") # this should not work
1696 self.assertEqual(return_code, 1)
1697 self.assertIn("Couldn't find the specified program: core", stderr)
1698
1700 """!
1701 Try to build and run an unknown target
1702 @return None
1703 """
1704 return_code, stdout, stderr = run_ns3("run nonsense") # this should not work
1705 self.assertEqual(return_code, 1)
1706 self.assertIn("Couldn't find the specified program: nonsense", stderr)
1707
1709 """!
1710 Try to run test-runner without building
1711 @return None
1712 """
1713 return_code, stdout, stderr = run_ns3('run "test-runner --list" --no-build --verbose')
1714 self.assertEqual(return_code, 0)
1715 self.assertNotIn("Built target test-runner", stdout)
1716 self.assertNotIn(cmake_build_target_command(target="test-runner"), stdout)
1717
1719 """!
1720 Test ns3 fails to run a library
1721 @return None
1722 """
1723 return_code, stdout, stderr = run_ns3("run core --no-build") # this should not work
1724 self.assertEqual(return_code, 1)
1725 self.assertIn("Couldn't find the specified program: core", stderr)
1726
1728 """!
1729 Test ns3 fails to run an unknown program
1730 @return None
1731 """
1732 return_code, stdout, stderr = run_ns3("run nonsense --no-build") # this should not work
1733 self.assertEqual(return_code, 1)
1734 self.assertIn("Couldn't find the specified program: nonsense", stderr)
1735
1737 """!
1738 Test if scratch simulator is executed through gdb and lldb
1739 @return None
1740 """
1741 if shutil.which("gdb") is None:
1742 self.skipTest("Missing gdb")
1743
1744 return_code, stdout, stderr = run_ns3("run scratch-simulator --gdb --verbose --no-build")
1745 self.assertEqual(return_code, 0)
1746 self.assertIn("scratch-simulator", stdout)
1747 self.assertIn("No debugging symbols found", stdout)
1748
1750 """!
1751 Test if scratch simulator is executed through valgrind
1752 @return None
1753 """
1754 if shutil.which("valgrind") is None:
1755 self.skipTest("Missing valgrind")
1756
1757 return_code, stdout, stderr = run_ns3("run scratch-simulator --valgrind --verbose --no-build")
1758 self.assertEqual(return_code, 0)
1759 self.assertIn("scratch-simulator", stderr)
1760 self.assertIn("Memcheck", stderr)
1761
1763 """!
1764 Test the doxygen target that does trigger a full build
1765 @return None
1766 """
1767 if shutil.which("doxygen") is None:
1768 self.skipTest("Missing doxygen")
1769
1770 doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
1771
1772 doxygen_files = ["introspected-command-line.h", "introspected-doxygen.h"]
1773 for filename in doxygen_files:
1774 file_path = os.sep.join([doc_folder, filename])
1775 if os.path.exists(file_path):
1776 os.remove(file_path)
1777
1778 # Rebuilding dot images is super slow, so not removing doxygen products
1779 # doxygen_build_folder = os.sep.join([doc_folder, "html"])
1780 # if os.path.exists(doxygen_build_folder):
1781 # shutil.rmtree(doxygen_build_folder)
1782
1783 return_code, stdout, stderr = run_ns3("docs doxygen")
1784 self.assertEqual(return_code, 0)
1785 self.assertIn(cmake_build_target_command(target="doxygen"), stdout)
1786 self.assertIn("Built target doxygen", stdout)
1787
1789 """!
1790 Test the doxygen target that doesn't trigger a full build
1791 @return None
1792 """
1793 if shutil.which("doxygen") is None:
1794 self.skipTest("Missing doxygen")
1795
1796 # Rebuilding dot images is super slow, so not removing doxygen products
1797 # doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
1798 # doxygen_build_folder = os.sep.join([doc_folder, "html"])
1799 # if os.path.exists(doxygen_build_folder):
1800 # shutil.rmtree(doxygen_build_folder)
1801
1802 return_code, stdout, stderr = run_ns3("docs doxygen-no-build")
1803 self.assertEqual(return_code, 0)
1804 self.assertIn(cmake_build_target_command(target="doxygen-no-build"), stdout)
1805 self.assertIn("Built target doxygen-no-build", stdout)
1806
1808 """!
1809 Test every individual target for Sphinx-based documentation
1810 @return None
1811 """
1812 if shutil.which("sphinx-build") is None:
1813 self.skipTest("Missing sphinx")
1814
1815 doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
1816
1817 # For each sphinx doc target.
1818 for target in ["contributing", "manual", "models", "tutorial"]:
1819 # First we need to clean old docs, or it will not make any sense.
1820 doc_build_folder = os.sep.join([doc_folder, target, "build"])
1821 doc_temp_folder = os.sep.join([doc_folder, target, "source-temp"])
1822 if os.path.exists(doc_build_folder):
1823 shutil.rmtree(doc_build_folder)
1824 if os.path.exists(doc_temp_folder):
1825 shutil.rmtree(doc_temp_folder)
1826
1827 # Build
1828 return_code, stdout, stderr = run_ns3("docs %s" % target)
1829 self.assertEqual(return_code, 0)
1830 self.assertIn(cmake_build_target_command(target="sphinx_%s" % target), stdout)
1831 self.assertIn("Built target sphinx_%s" % target, stdout)
1832
1833 # Check if the docs output folder exists
1834 doc_build_folder = os.sep.join([doc_folder, target, "build"])
1835 self.assertTrue(os.path.exists(doc_build_folder))
1836
1837 # Check if the all the different types are in place (latex, split HTML and single page HTML)
1838 for build_type in ["latex", "html", "singlehtml"]:
1839 self.assertTrue(os.path.exists(os.sep.join([doc_build_folder, build_type])))
1840
1842 """!
1843 Test the documentation target that builds
1844 both doxygen and sphinx based documentation
1845 @return None
1846 """
1847 if shutil.which("doxygen") is None:
1848 self.skipTest("Missing doxygen")
1849 if shutil.which("sphinx-build") is None:
1850 self.skipTest("Missing sphinx")
1851
1852 doc_folder = os.path.abspath(os.sep.join([".", "doc"]))
1853
1854 # First we need to clean old docs, or it will not make any sense.
1855
1856 # Rebuilding dot images is super slow, so not removing doxygen products
1857 # doxygen_build_folder = os.sep.join([doc_folder, "html"])
1858 # if os.path.exists(doxygen_build_folder):
1859 # shutil.rmtree(doxygen_build_folder)
1860
1861 for target in ["manual", "models", "tutorial"]:
1862 doc_build_folder = os.sep.join([doc_folder, target, "build"])
1863 if os.path.exists(doc_build_folder):
1864 shutil.rmtree(doc_build_folder)
1865
1866 return_code, stdout, stderr = run_ns3("docs all")
1867 self.assertEqual(return_code, 0)
1868 self.assertIn(cmake_build_target_command(target="sphinx"), stdout)
1869 self.assertIn("Built target sphinx", stdout)
1870 self.assertIn(cmake_build_target_command(target="doxygen"), stdout)
1871 self.assertIn("Built target doxygen", stdout)
1872
1874 """!
1875 Try to set ownership of scratch-simulator from current user to root,
1876 and change execution permissions
1877 @return None
1878 """
1879
1880 # Test will be skipped if not defined
1881 sudo_password = os.getenv("SUDO_PASSWORD", None)
1882
1883 # Skip test if variable containing sudo password is the default value
1884 if sudo_password is None:
1885 self.skipTest("SUDO_PASSWORD environment variable was not specified")
1886
1887 enable_sudo = read_lock_entry("ENABLE_SUDO")
1888 self.assertFalse(enable_sudo is True)
1889
1890 # First we run to ensure the program was built
1891 return_code, stdout, stderr = run_ns3('run scratch-simulator')
1892 self.assertEqual(return_code, 0)
1893 self.assertIn("Built target scratch_scratch-simulator", stdout)
1894 self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout)
1895 scratch_simulator_path = list(filter(lambda x: x if "scratch-simulator" in x else None,
1897 )
1898 )[-1]
1899 prev_fstat = os.stat(scratch_simulator_path) # we get the permissions before enabling sudo
1900
1901 # Now try setting the sudo bits from the run subparser
1902 return_code, stdout, stderr = run_ns3('run scratch-simulator --enable-sudo',
1903 env={"SUDO_PASSWORD": sudo_password})
1904 self.assertEqual(return_code, 0)
1905 self.assertIn("Built target scratch_scratch-simulator", stdout)
1906 self.assertIn(cmake_build_target_command(target="scratch_scratch-simulator"), stdout)
1907 fstat = os.stat(scratch_simulator_path)
1908
1909 import stat
1910 # If we are on Windows, these permissions mean absolutely nothing,
1911 # and on Fuse builds they might not make any sense, so we need to skip before failing
1912 likely_fuse_mount = ((prev_fstat.st_mode & stat.S_ISUID) == (fstat.st_mode & stat.S_ISUID)) and \
1913 prev_fstat.st_uid == 0
1914
1915 if sys.platform == "win32" or likely_fuse_mount:
1916 self.skipTest("Windows or likely a FUSE mount")
1917
1918 # If this is a valid platform, we can continue
1919 self.assertEqual(fstat.st_uid, 0) # check the file was correctly chown'ed by root
1920 self.assertEqual(fstat.st_mode & stat.S_ISUID, stat.S_ISUID) # check if normal users can run as sudo
1921
1922 # Now try setting the sudo bits as a post-build step (as set by configure subparser)
1923 return_code, stdout, stderr = run_ns3('configure --enable-sudo')
1924 self.assertEqual(return_code, 0)
1925
1926 # Check if it was properly set in the buildstatus file
1927 enable_sudo = read_lock_entry("ENABLE_SUDO")
1928 self.assertTrue(enable_sudo)
1929
1930 # Remove old executables
1931 for executable in self.ns3_executablesns3_executables:
1932 if os.path.exists(executable):
1933 os.remove(executable)
1934
1935 # Try to build and then set sudo bits as a post-build step
1936 return_code, stdout, stderr = run_ns3('build', env={"SUDO_PASSWORD": sudo_password})
1937 self.assertEqual(return_code, 0)
1938
1939 # Check if commands are being printed for every target
1940 self.assertIn("chown root", stdout)
1941 self.assertIn("chmod u+s", stdout)
1942 for executable in self.ns3_executablesns3_executables:
1943 self.assertIn(os.path.basename(executable), stdout)
1944
1945 # Check scratch simulator yet again
1946 fstat = os.stat(scratch_simulator_path)
1947 self.assertEqual(fstat.st_uid, 0) # check the file was correctly chown'ed by root
1948 self.assertEqual(fstat.st_mode & stat.S_ISUID, stat.S_ISUID) # check if normal users can run as sudo
1949
1951 """!
1952 Check if command template is working
1953 @return None
1954 """
1955
1956 # Command templates that are empty or do not have a '%s' should fail
1957 return_code0, stdout0, stderr0 = run_ns3('run sample-simulator --command-template')
1958 self.assertEqual(return_code0, 2)
1959 self.assertIn("argument --command-template: expected one argument", stderr0)
1960
1961 return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template=" "')
1962 return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --command-template " "')
1963 return_code3, stdout3, stderr3 = run_ns3('run sample-simulator --command-template "echo "')
1964 self.assertEqual((return_code1, return_code2, return_code3), (1, 1, 1))
1965 self.assertIn("not all arguments converted during string formatting", stderr1)
1966 self.assertEqual(stderr1, stderr2)
1967 self.assertEqual(stderr2, stderr3)
1968
1969 # Command templates with %s should at least continue and try to run the target
1970 return_code4, stdout4, _ = run_ns3('run sample-simulator --command-template "%s --PrintVersion" --verbose')
1971 return_code5, stdout5, _ = run_ns3('run sample-simulator --command-template="%s --PrintVersion" --verbose')
1972 self.assertEqual((return_code4, return_code5), (0, 0))
1973 self.assertIn("sample-simulator --PrintVersion", stdout4)
1974 self.assertIn("sample-simulator --PrintVersion", stdout5)
1975
1977 """!
1978 Check if all flavors of different argument passing to
1979 executable targets are working
1980 @return None
1981 """
1982
1983 # Test if all argument passing flavors are working
1984 return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help" --verbose')
1985 return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template="%s --help" --verbose')
1986 return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --verbose -- --help')
1987
1988 self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
1989 self.assertIn("sample-simulator --help", stdout0)
1990 self.assertIn("sample-simulator --help", stdout1)
1991 self.assertIn("sample-simulator --help", stdout2)
1992
1993 # Test if the same thing happens with an additional run argument (e.g. --no-build)
1994 return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --help" --no-build')
1995 return_code1, stdout1, stderr1 = run_ns3('run sample-simulator --command-template="%s --help" --no-build')
1996 return_code2, stdout2, stderr2 = run_ns3('run sample-simulator --no-build -- --help')
1997 self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
1998 self.assertEqual(stdout0, stdout1)
1999 self.assertEqual(stdout1, stdout2)
2000 self.assertEqual(stderr0, stderr1)
2001 self.assertEqual(stderr1, stderr2)
2002
2003 # Now collect results for each argument individually
2004 return_code0, stdout0, stderr0 = run_ns3('run "sample-simulator --PrintGlobals" --verbose')
2005 return_code1, stdout1, stderr1 = run_ns3('run "sample-simulator --PrintGroups" --verbose')
2006 return_code2, stdout2, stderr2 = run_ns3('run "sample-simulator --PrintTypeIds" --verbose')
2007
2008 self.assertEqual((return_code0, return_code1, return_code2), (0, 0, 0))
2009 self.assertIn("sample-simulator --PrintGlobals", stdout0)
2010 self.assertIn("sample-simulator --PrintGroups", stdout1)
2011 self.assertIn("sample-simulator --PrintTypeIds", stdout2)
2012
2013 # Then check if all the arguments are correctly merged by checking the outputs
2014 cmd = 'run "sample-simulator --PrintGlobals" --command-template="%s --PrintGroups" --verbose -- --PrintTypeIds'
2015 return_code, stdout, stderr = run_ns3(cmd)
2016 self.assertEqual(return_code, 0)
2017
2018 # The order of the arguments is command template,
2019 # arguments passed with the target itself
2020 # and forwarded arguments after the -- separator
2021 self.assertIn("sample-simulator --PrintGroups --PrintGlobals --PrintTypeIds", stdout)
2022
2023 # Check if it complains about the missing -- separator
2024 cmd0 = 'run sample-simulator --command-template="%s " --PrintTypeIds'
2025 cmd1 = 'run sample-simulator --PrintTypeIds'
2026
2027 return_code0, stdout0, stderr0 = run_ns3(cmd0)
2028 return_code1, stdout1, stderr1 = run_ns3(cmd1)
2029 self.assertEqual((return_code0, return_code1), (1, 1))
2030 self.assertIn("To forward configuration or runtime options, put them after '--'", stderr0)
2031 self.assertIn("To forward configuration or runtime options, put them after '--'", stderr1)
2032
2034 """!
2035 Test if scratch simulator is executed through lldb
2036 @return None
2037 """
2038 if shutil.which("lldb") is None:
2039 self.skipTest("Missing lldb")
2040
2041 return_code, stdout, stderr = run_ns3("run scratch-simulator --lldb --verbose --no-build")
2042 self.assertEqual(return_code, 0)
2043 self.assertIn("scratch-simulator", stdout)
2044 self.assertIn("(lldb) target create", stdout)
2045
2046
2047if __name__ == '__main__':
2048 loader = unittest.TestLoader()
2049 suite = unittest.TestSuite()
2050
2051 # Put tests cases in order
2052 suite.addTests(loader.loadTestsFromTestCase(NS3UnusedSourcesTestCase))
2053 suite.addTests(loader.loadTestsFromTestCase(NS3CommonSettingsTestCase))
2054 suite.addTests(loader.loadTestsFromTestCase(NS3ConfigureBuildProfileTestCase))
2055 suite.addTests(loader.loadTestsFromTestCase(NS3ConfigureTestCase))
2056 suite.addTests(loader.loadTestsFromTestCase(NS3BuildBaseTestCase))
2057 suite.addTests(loader.loadTestsFromTestCase(NS3ExpectedUseTestCase))
2058
2059 # Generate a dictionary of test names and their objects
2060 tests = dict(map(lambda x: (x._testMethodName, x), suite._tests))
2061
2062 # Filter tests by name
2063 # name_to_search = ""
2064 # tests_to_run = set(map(lambda x: x if name_to_search in x else None, tests.keys()))
2065 # tests_to_remove = set(tests) - set(tests_to_run)
2066 # for test_to_remove in tests_to_remove:
2067 # suite._tests.remove(tests[test_to_remove])
2068
2069 # Before running, check if ns3rc exists and save it
2070 ns3rc_script_bak = ns3rc_script + ".bak"
2071 if os.path.exists(ns3rc_script) and not os.path.exists(ns3rc_script_bak):
2072 shutil.move(ns3rc_script, ns3rc_script_bak)
2073
2074 # Run tests and fail as fast as possible
2075 runner = unittest.TextTestRunner(failfast=True)
2076 result = runner.run(suite)
2077
2078 # After completing the tests successfully, restore the ns3rc file
2079 if os.path.exists(ns3rc_script_bak):
2080 shutil.move(ns3rc_script_bak, ns3rc_script)
#define max(a, b)
Definition: 80211b.c:43
Generic test case with basic function inherited by more complex tests.
Definition: test-ns3.py:431
def config_ok(self, return_code, stdout)
Check if configuration for release mode worked normally.
Definition: test-ns3.py:439
ns3_executables
ns3_executables holds a list of executables in .lock-ns3
Definition: test-ns3.py:472
def setUp(self)
Clean configuration/build artifacts before testing configuration and build settings After configuring...
Definition: test-ns3.py:450
ns3_modules
ns3_modules holds a list to the modules enabled stored in .lock-ns3
Definition: test-ns3.py:477
Tests ns3 regarding building the project.
Definition: test-ns3.py:1108
def test_06_TestVersionFile(self)
Test if changing the version file affects the library names.
Definition: test-ns3.py:1189
def test_01_BuildExistingTargets(self)
Try building the core library.
Definition: test-ns3.py:1128
def test_10_PybindgenBindings(self)
Test if cmake is calling pybindgen through modulegen to generate the bindings source files correctly.
Definition: test-ns3.py:1466
def test_08_InstallationAndUninstallation(self)
Tries setting a ns3 version, then installing it.
Definition: test-ns3.py:1301
def test_11_AmbiguityCheck(self)
Test if ns3 can alert correctly in case a shortcut collision happens.
Definition: test-ns3.py:1575
def setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned.
Definition: test-ns3.py:1116
def test_02_BuildNonExistingTargets(self)
Try building core-test library without tests enabled.
Definition: test-ns3.py:1137
def test_04_BuildProjectNoTaskLines(self)
Try hiding task lines.
Definition: test-ns3.py:1159
def test_03_BuildProject(self)
Try building the project:
Definition: test-ns3.py:1147
def test_09_Scratches(self)
Tries to build scratch-simulator and subdir/scratch-simulator-subdir.
Definition: test-ns3.py:1437
def test_05_BreakBuild(self)
Try removing an essential file to break the build.
Definition: test-ns3.py:1168
ns3_executables
ns3_executables holds a list of executables in .lock-ns3
Definition: test-ns3.py:1246
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:1233
ns3_libraries
ns3_libraries holds a list of built module libraries
Definition: test-ns3.py:1126
ns3 tests related to generic options
Definition: test-ns3.py:290
def test_05_CheckVersion(self)
Test only passing 'show version' argument to ns3.
Definition: test-ns3.py:340
def setUp(self)
Clean configuration/build artifacts before common commands.
Definition: test-ns3.py:295
def test_01_NoOption(self)
Test not passing any arguments to.
Definition: test-ns3.py:304
def test_02_NoTaskLines(self)
Test only passing –quiet argument to ns3.
Definition: test-ns3.py:313
def test_03_CheckConfig(self)
Test only passing 'show config' argument to ns3.
Definition: test-ns3.py:322
def test_04_CheckProfile(self)
Test only passing 'show profile' argument to ns3.
Definition: test-ns3.py:331
ns3 tests related to build profiles
Definition: test-ns3.py:350
def test_05_TYPO(self)
Test a build type with another typo.
Definition: test-ns3.py:421
def test_02_Release(self)
Test the release build.
Definition: test-ns3.py:383
def test_01_Debug(self)
Test the debug build.
Definition: test-ns3.py:364
def setUp(self)
Clean configuration/build artifacts before testing configuration settings.
Definition: test-ns3.py:355
def test_03_Optimized(self)
Test the optimized build.
Definition: test-ns3.py:393
def test_04_Typo(self)
Test a build type with a typo.
Definition: test-ns3.py:412
Test ns3 configuration options.
Definition: test-ns3.py:480
def setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned.
Definition: test-ns3.py:488
def test_06_DisableModulesComma(self)
Test disabling comma-separated (waf-style) examples.
Definition: test-ns3.py:618
def test_04_DisableModules(self)
Test disabling specific modules.
Definition: test-ns3.py:574
def test_03_EnableModules(self)
Test enabling specific modules.
Definition: test-ns3.py:547
def test_02_Tests(self)
Test enabling and disabling tests.
Definition: test-ns3.py:520
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:758
def test_12_CheckVersion(self)
Test passing 'show version' argument to ns3 to get the build version.
Definition: test-ns3.py:836
def test_05_EnableModulesComma(self)
Test enabling comma-separated (waf-style) examples.
Definition: test-ns3.py:596
def test_01_Examples(self)
Test enabling and disabling examples.
Definition: test-ns3.py:498
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:920
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:1054
def test_17_CMakePerformanceTracing(self)
Test if CMake performance tracing works and produces the cmake_performance_trace.log file.
Definition: test-ns3.py:1096
def test_07_Ns3rc(self)
Test loading settings from the ns3rc config file.
Definition: test-ns3.py:640
def test_13_Scratches(self)
Test if CMake target names for scratches and ns3 shortcuts are working correctly.
Definition: test-ns3.py:848
def test_08_DryRun(self)
Test dry-run (printing commands to be executed instead of running them)
Definition: test-ns3.py:702
def test_10_CheckConfig(self)
Test passing 'show config' argument to ns3 to get the configuration table.
Definition: test-ns3.py:818
def test_15_InvalidLibrariesToLink(self)
Test if CMake and ns3 fail in the expected ways when:
Definition: test-ns3.py:962
def test_11_CheckProfile(self)
Test passing 'show profile' argument to ns3 to get the build profile.
Definition: test-ns3.py:827
Tests ns3 usage in more realistic scenarios.
Definition: test-ns3.py:1630
def test_10_DoxygenWithBuild(self)
Test the doxygen target that does trigger a full build.
Definition: test-ns3.py:1762
def test_02_BuildAndRunExistingExecutableTarget(self)
Try to build and run test-runner.
Definition: test-ns3.py:1680
def test_08_RunNoBuildGdb(self)
Test if scratch simulator is executed through gdb and lldb.
Definition: test-ns3.py:1736
def test_05_RunNoBuildExistingExecutableTarget(self)
Try to run test-runner without building.
Definition: test-ns3.py:1708
def test_06_RunNoBuildExistingLibraryTarget(self)
Test ns3 fails to run a library.
Definition: test-ns3.py:1718
def test_03_BuildAndRunExistingLibraryTarget(self)
Try to build and run a library.
Definition: test-ns3.py:1690
def test_01_BuildProject(self)
Try to build the project.
Definition: test-ns3.py:1665
ns3_modules
ns3_modules holds a list to the modules enabled stored in .lock-ns3
Definition: test-ns3.py:1663
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:1873
def test_16_ForwardArgumentsToRunTargets(self)
Check if all flavors of different argument passing to executable targets are working.
Definition: test-ns3.py:1976
def test_17_RunNoBuildLldb(self)
Test if scratch simulator is executed through lldb.
Definition: test-ns3.py:2033
def test_15_CommandTemplate(self)
Check if command template is working.
Definition: test-ns3.py:1950
def test_04_BuildAndRunNonExistingTarget(self)
Try to build and run an unknown target.
Definition: test-ns3.py:1699
def test_07_RunNoBuildNonExistingExecutableTarget(self)
Test ns3 fails to run an unknown program.
Definition: test-ns3.py:1727
ns3_executables
ns3_executables holds a list of executables in .lock-ns3
Definition: test-ns3.py:1657
def test_09_RunNoBuildValgrind(self)
Test if scratch simulator is executed through valgrind.
Definition: test-ns3.py:1749
def test_13_Documentation(self)
Test the documentation target that builds both doxygen and sphinx based documentation.
Definition: test-ns3.py:1841
def setUp(self)
Reuse cleaning/release configuration from NS3BaseTestCase if flag is cleaned Here examples,...
Definition: test-ns3.py:1638
def test_11_DoxygenWithoutBuild(self)
Test the doxygen target that doesn't trigger a full build.
Definition: test-ns3.py:1788
def test_12_SphinxDocumentation(self)
Test every individual target for Sphinx-based documentation.
Definition: test-ns3.py:1807
ns-3 tests related to checking if source files were left behind, not being used by CMake
Definition: test-ns3.py:170
dictionary directory_and_files
dictionary containing directories with .cc source files
Definition: test-ns3.py:176
def test_01_UnusedExampleSources(self)
Test if all example source files are being used in their respective CMakeLists.txt.
Definition: test-ns3.py:194
def setUp(self)
Scan all C++ source files and add them to a list based on their path.
Definition: test-ns3.py:178
def test_02_UnusedModuleSources(self)
Test if all module source files are being used in their respective CMakeLists.txt.
Definition: test-ns3.py:217
def test_03_UnusedUtilsSources(self)
Test if all utils source files are being used in their respective CMakeLists.txt.
Definition: test-ns3.py:259
#define NS_ABORT_IF(cond)
Abnormal program termination if a condition is true.
Definition: abort.h:77
string project
Definition: conf.py:43
def get_programs_list()
Extracts the programs list from .lock-ns3.
Definition: test-ns3.py:113
def get_libraries_list(lib_outdir=usual_lib_outdir)
Gets a list of built libraries.
Definition: test-ns3.py:124
def get_test_enabled()
Check if tests are enabled in the .lock-ns3.
Definition: test-ns3.py:154
def read_lock_entry(entry)
Read interesting entries from the .lock-ns3 file.
Definition: test-ns3.py:142
cmake_build_target_command
Definition: test-ns3.py:47
def get_headers_list(outdir=usual_outdir)
Gets a list of header files.
Definition: test-ns3.py:133
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:68
def get_enabled_modules()
Definition: test-ns3.py:162
def run_ns3(args, env=None)
Runs the ns3 wrapper script with arguments.
Definition: test-ns3.py:52
#define list