12 import Params |
10 import Params |
13 import Object |
11 import Object |
14 import ccroot |
12 import ccroot |
15 import Task |
13 import Task |
16 |
14 |
|
15 import wutils |
|
16 import regression |
|
17 |
17 Params.g_autoconfig = 1 |
18 Params.g_autoconfig = 1 |
18 |
19 |
19 # the following two variables are used by the target "waf dist" |
20 # the following two variables are used by the target "waf dist" |
20 VERSION = file("VERSION").read().strip() |
21 VERSION = file("VERSION").read().strip() |
21 APPNAME = 'ns' |
22 APPNAME = 'ns' |
22 |
23 |
|
24 wutils.VERSION = VERSION |
|
25 wutils.APPNAME = APPNAME |
|
26 |
23 # these variables are mandatory ('/' are converted automatically) |
27 # these variables are mandatory ('/' are converted automatically) |
24 srcdir = '.' |
28 srcdir = '.' |
25 blddir = 'build' |
29 blddir = 'build' |
26 |
|
27 # |
|
28 # The directory in which the tarball of the reference traces lives. This is |
|
29 # used if Mercurial is not on the system. |
|
30 # |
|
31 REGRESSION_TRACES_URL = "http://www.nsnam.org/releases/" |
|
32 |
|
33 # |
|
34 # The path to the Mercurial repository used to find the reference traces if |
|
35 # we find "hg" on the system. We expect that the repository will be named |
|
36 # identically to refDirName below |
|
37 # |
|
38 REGRESSION_TRACES_REPO = "http://code.nsnam.org/" |
|
39 |
|
40 # |
|
41 # Name of the local directory where the regression code lives. |
|
42 # |
|
43 REGRESSION_DIR = "regression" |
|
44 |
|
45 # |
|
46 # The last part of the path name to use to find the regression traces. The |
|
47 # path will be APPNAME + '-' + VERSION + REGRESSION_SUFFIX, e.g., |
|
48 # ns-3-dev-ref-traces |
|
49 # |
|
50 REGRESSION_SUFFIX = "-ref-traces" |
|
51 |
|
52 # |
|
53 # The last part of the path name to use to find the regression traces tarball. |
|
54 # path will be APPNAME + '-' + VERSION + REGRESSION_SUFFIX + TRACEBALL_SUFFIX, |
|
55 # e.g., ns-3-dev-ref-traces.tar.bz2 |
|
56 # |
|
57 TRACEBALL_SUFFIX = ".tar.bz2" |
|
58 |
30 |
59 |
31 |
60 def dist_hook(): |
32 def dist_hook(): |
61 import tarfile |
33 import tarfile |
62 shutil.rmtree("doc/html", True) |
34 shutil.rmtree("doc/html", True) |
66 if not os.path.exists("bindings/python/pybindgen"): |
38 if not os.path.exists("bindings/python/pybindgen"): |
67 Params.fatal("Missing pybindgen checkout; run './waf configure --pybindgen-checkout' first.") |
39 Params.fatal("Missing pybindgen checkout; run './waf configure --pybindgen-checkout' first.") |
68 |
40 |
69 ## build the name of the traces subdirectory. Will be something like |
41 ## build the name of the traces subdirectory. Will be something like |
70 ## ns-3-dev-ref-traces |
42 ## ns-3-dev-ref-traces |
71 traces_name = APPNAME + '-' + VERSION + REGRESSION_SUFFIX |
43 traces_name = APPNAME + '-' + VERSION + regression.REGRESSION_SUFFIX |
72 ## Create a tar.bz2 file with the traces |
44 ## Create a tar.bz2 file with the traces |
73 traces_dir = os.path.join(REGRESSION_DIR, traces_name) |
45 traces_dir = os.path.join(regression.REGRESSION_DIR, traces_name) |
74 if not os.path.isdir(traces_dir): |
46 if not os.path.isdir(traces_dir): |
75 Params.warning("Not creating traces archive: the %s directory does not exist" % traces_dir) |
47 Params.warning("Not creating traces archive: the %s directory does not exist" % traces_dir) |
76 else: |
48 else: |
77 traceball = traces_name + TRACEBALL_SUFFIX |
49 traceball = traces_name + wutils.TRACEBALL_SUFFIX |
78 tar = tarfile.open(os.path.join("..", traceball), 'w:bz2') |
50 tar = tarfile.open(os.path.join("..", traceball), 'w:bz2') |
79 tar.add(traces_dir) |
51 tar.add(traces_dir) |
80 tar.close() |
52 tar.close() |
81 ## Now remove it; we do not ship the traces with the main tarball... |
53 ## Now remove it; we do not ship the traces with the main tarball... |
82 shutil.rmtree(traces_dir, True) |
54 shutil.rmtree(traces_dir, True) |
490 if not env['DIFF']: |
462 if not env['DIFF']: |
491 Params.fatal("Cannot run regression tests: the 'diff' program is not installed.") |
463 Params.fatal("Cannot run regression tests: the 'diff' program is not installed.") |
492 _dir = os.getcwd() |
464 _dir = os.getcwd() |
493 os.chdir("regression") |
465 os.chdir("regression") |
494 try: |
466 try: |
495 run_regression() |
467 regression.run_regression() |
496 finally: |
468 finally: |
497 os.chdir(_dir) |
469 os.chdir(_dir) |
498 |
470 |
499 if Params.g_options.lcov_report: |
471 if Params.g_options.lcov_report: |
500 lcov_report() |
472 lcov_report() |
501 |
473 |
502 if Params.g_options.run: |
474 if Params.g_options.run: |
503 run_program(Params.g_options.run, get_command_template()) |
475 wutils.run_program(Params.g_options.run, get_command_template()) |
504 raise SystemExit(0) |
476 raise SystemExit(0) |
505 |
477 |
506 if Params.g_options.pyrun: |
478 if Params.g_options.pyrun: |
507 run_python_program(Params.g_options.pyrun) |
479 wutils.run_python_program(Params.g_options.pyrun) |
508 raise SystemExit(0) |
480 raise SystemExit(0) |
509 |
481 |
510 def _run_waf_check(): |
482 def _run_waf_check(): |
511 ## generate the trace sources list docs |
483 ## generate the trace sources list docs |
512 env = Params.g_build.env_of_name('default') |
484 env = Params.g_build.env_of_name('default') |
513 proc_env = _get_proc_env() |
485 proc_env = wutils.get_proc_env() |
514 try: |
486 try: |
515 program_obj = _find_program('print-introspected-doxygen', env) |
487 program_obj = wutils.find_program('print-introspected-doxygen', env) |
516 except ValueError: # could happen if print-introspected-doxygen is |
488 except ValueError: # could happen if print-introspected-doxygen is |
517 # not built because of waf configure |
489 # not built because of waf configure |
518 # --enable-modules=xxx |
490 # --enable-modules=xxx |
519 pass |
491 pass |
520 else: |
492 else: |
523 if subprocess.Popen([prog], stdout=out, env=proc_env).wait(): |
495 if subprocess.Popen([prog], stdout=out, env=proc_env).wait(): |
524 raise SystemExit(1) |
496 raise SystemExit(1) |
525 out.close() |
497 out.close() |
526 |
498 |
527 print "-- Running NS-3 C++ core unit tests..." |
499 print "-- Running NS-3 C++ core unit tests..." |
528 run_program('run-tests', get_command_template()) |
500 wutils.run_program('run-tests', get_command_template()) |
529 |
501 |
530 if env['ENABLE_PYTHON_BINDINGS']: |
502 if env['ENABLE_PYTHON_BINDINGS']: |
531 print "-- Running NS-3 Python bindings unit tests..." |
503 print "-- Running NS-3 Python bindings unit tests..." |
532 _run_argv([env['PYTHON'], os.path.join("utils", "python-unit-tests.py")], proc_env) |
504 wutils.run_argv([env['PYTHON'], os.path.join("utils", "python-unit-tests.py")], proc_env) |
533 else: |
505 else: |
534 print "-- Skipping NS-3 Python bindings unit tests: Python bindings not enabled." |
506 print "-- Skipping NS-3 Python bindings unit tests: Python bindings not enabled." |
535 |
507 |
536 |
508 |
537 def _find_program(program_name, env): |
|
538 launch_dir = os.path.abspath(Params.g_cwd_launch) |
|
539 found_programs = [] |
|
540 for obj in Object.g_allobjs: |
|
541 if not getattr(obj, 'is_ns3_program', False): |
|
542 continue |
|
543 |
|
544 ## filter out programs not in the subtree starting at the launch dir |
|
545 if not (obj.path.abspath().startswith(launch_dir) |
|
546 or obj.path.abspath(env).startswith(launch_dir)): |
|
547 continue |
|
548 |
|
549 found_programs.append(obj.target) |
|
550 if obj.target == program_name: |
|
551 return obj |
|
552 raise ValueError("program '%s' not found; available programs are: %r" |
|
553 % (program_name, found_programs)) |
|
554 |
|
555 def _get_proc_env(os_env=None): |
|
556 env = Params.g_build.env_of_name('default') |
|
557 if sys.platform == 'linux2': |
|
558 pathvar = 'LD_LIBRARY_PATH' |
|
559 elif sys.platform == 'darwin': |
|
560 pathvar = 'DYLD_LIBRARY_PATH' |
|
561 elif sys.platform == 'win32': |
|
562 pathvar = 'PATH' |
|
563 elif sys.platform == 'cygwin': |
|
564 pathvar = 'PATH' |
|
565 elif sys.platform.startswith('freebsd'): |
|
566 pathvar = 'LD_LIBRARY_PATH' |
|
567 else: |
|
568 Params.warning(("Don't know how to configure " |
|
569 "dynamic library path for the platform %r;" |
|
570 " assuming it's LD_LIBRARY_PATH.") % (sys.platform,)) |
|
571 pathvar = 'LD_LIBRARY_PATH' |
|
572 |
|
573 proc_env = dict(os.environ) |
|
574 if os_env is not None: |
|
575 proc_env.update(os_env) |
|
576 |
|
577 if pathvar is not None: |
|
578 if pathvar in proc_env: |
|
579 proc_env[pathvar] = os.pathsep.join(list(env['NS3_MODULE_PATH']) + [proc_env[pathvar]]) |
|
580 else: |
|
581 proc_env[pathvar] = os.pathsep.join(list(env['NS3_MODULE_PATH'])) |
|
582 |
|
583 pymoddir = Params.g_build.m_curdirnode.find_dir('bindings/python').abspath(env) |
|
584 if 'PYTHONPATH' in proc_env: |
|
585 proc_env['PYTHONPATH'] = os.pathsep.join([pymoddir] + [proc_env['PYTHONPATH']]) |
|
586 else: |
|
587 proc_env['PYTHONPATH'] = pymoddir |
|
588 |
|
589 return proc_env |
|
590 |
|
591 def _run_argv(argv, os_env=None): |
|
592 proc_env = _get_proc_env(os_env) |
|
593 env = Params.g_build.env_of_name('default') |
|
594 retval = subprocess.Popen(argv, env=proc_env).wait() |
|
595 if retval: |
|
596 Params.fatal("Command %s exited with code %i" % (argv, retval)) |
|
597 |
|
598 |
|
599 def run_program(program_string, command_template=None): |
|
600 """ |
|
601 if command_template is not None, then program_string == program |
|
602 name and argv is given by command_template with %s replaced by the |
|
603 full path to the program. Else, program_string is interpreted as |
|
604 a shell command with first name being the program name. |
|
605 """ |
|
606 env = Params.g_build.env_of_name('default') |
|
607 |
|
608 if command_template in (None, '%s'): |
|
609 argv = shlex.split(program_string) |
|
610 program_name = argv[0] |
|
611 |
|
612 try: |
|
613 program_obj = _find_program(program_name, env) |
|
614 except ValueError, ex: |
|
615 Params.fatal(str(ex)) |
|
616 |
|
617 try: |
|
618 program_node = program_obj.path.find_build(ccroot.get_target_name(program_obj)) |
|
619 except AttributeError: |
|
620 Params.fatal("%s does not appear to be a program" % (program_name,)) |
|
621 |
|
622 execvec = [program_node.abspath(env)] + argv[1:] |
|
623 |
|
624 else: |
|
625 |
|
626 program_name = program_string |
|
627 try: |
|
628 program_obj = _find_program(program_name, env) |
|
629 except ValueError, ex: |
|
630 Params.fatal(str(ex)) |
|
631 try: |
|
632 program_node = program_obj.path.find_build(ccroot.get_target_name(program_obj)) |
|
633 except AttributeError: |
|
634 Params.fatal("%s does not appear to be a program" % (program_name,)) |
|
635 |
|
636 execvec = shlex.split(command_template % (program_node.abspath(env),)) |
|
637 |
|
638 former_cwd = os.getcwd() |
|
639 if (Params.g_options.cwd_launch): |
|
640 os.chdir(Params.g_options.cwd_launch) |
|
641 else: |
|
642 os.chdir(Params.g_cwd_launch) |
|
643 try: |
|
644 retval = _run_argv(execvec) |
|
645 finally: |
|
646 os.chdir(former_cwd) |
|
647 |
|
648 return retval |
|
649 |
|
650 |
|
651 |
|
652 def run_python_program(program_string): |
|
653 env = Params.g_build.env_of_name('default') |
|
654 execvec = shlex.split(program_string) |
|
655 |
|
656 former_cwd = os.getcwd() |
|
657 if (Params.g_options.cwd_launch): |
|
658 os.chdir(Params.g_options.cwd_launch) |
|
659 else: |
|
660 os.chdir(Params.g_cwd_launch) |
|
661 try: |
|
662 retval = _run_argv([env['PYTHON']] + execvec) |
|
663 finally: |
|
664 os.chdir(former_cwd) |
|
665 |
|
666 return retval |
|
667 |
509 |
668 |
510 |
669 def check_shell(): |
511 def check_shell(): |
670 if 'NS3_MODULE_PATH' not in os.environ: |
512 if 'NS3_MODULE_PATH' not in os.environ: |
671 return |
513 return |
849 return TMPFOLDER |
691 return TMPFOLDER |
850 |
692 |
851 Scripting.DistDir = DistDir |
693 Scripting.DistDir = DistDir |
852 |
694 |
853 |
695 |
854 def dev_null(): |
|
855 if sys.platform == 'win32': |
|
856 return open("NUL:", "w") |
|
857 else: |
|
858 return open("/dev/null", "w") |
|
859 |
|
860 |
|
861 ### Regression testing |
|
862 class Regression(object): |
|
863 def __init__(self, testdir): |
|
864 self.testdir = testdir |
|
865 self.env = Params.g_build.env_of_name('default') |
|
866 |
|
867 def run_test(self, verbose, generate, refDirName, testName, arguments=[], pyscript=None, refTestName=None): |
|
868 """ |
|
869 @param verbose: enable verbose execution |
|
870 |
|
871 @param generate: generate new traces instead of comparing with the reference |
|
872 |
|
873 @param refDirName: name of the base directory containing reference traces |
|
874 |
|
875 @param testName: name of the test |
|
876 |
|
877 @arguments: list of extra parameters to pass to the program to be tested |
|
878 |
|
879 @pyscript: if not None, the test is written in Python and this |
|
880 parameter contains the path to the python script, relative to |
|
881 the project root dir |
|
882 |
|
883 @param refTestName: if not None, this is the name of the directory under refDirName |
|
884 that contains the reference traces. Otherwise "refDirname/testName + .ref" is used. |
|
885 |
|
886 """ |
|
887 if not isinstance(arguments, list): |
|
888 raise TypeError |
|
889 |
|
890 if refTestName is None: |
|
891 refTestDirName = os.path.join(refDirName, (testName + ".ref")) |
|
892 else: |
|
893 refTestDirName = os.path.join(refDirName, refTestName) |
|
894 |
|
895 if not os.path.exists(refDirName): |
|
896 print"No reference trace repository" |
|
897 return 1 |
|
898 |
|
899 if generate: |
|
900 if not os.path.exists(refTestDirName): |
|
901 print "creating new " + refTestDirName |
|
902 os.mkdir(refTestDirName) |
|
903 |
|
904 if pyscript is None: |
|
905 Params.g_options.cwd_launch = refTestDirName |
|
906 tmpl = "%s" |
|
907 for arg in arguments: |
|
908 tmpl = tmpl + " " + arg |
|
909 run_program(testName, tmpl) |
|
910 else: |
|
911 argv = [self.env['PYTHON'], os.path.join('..', '..', '..', *os.path.split(pyscript))] + arguments |
|
912 before = os.getcwd() |
|
913 os.chdir(refTestDirName) |
|
914 try: |
|
915 _run_argv(argv) |
|
916 finally: |
|
917 os.chdir(before) |
|
918 print "Remember to commit " + refTestDirName |
|
919 return 0 |
|
920 else: |
|
921 if not os.path.exists(refTestDirName): |
|
922 print "Cannot locate reference traces in " + refTestDirName |
|
923 return 1 |
|
924 |
|
925 |
|
926 if refTestName is None: |
|
927 traceDirName = testName + ".ref" |
|
928 else: |
|
929 traceDirName = refTestName |
|
930 traceDirName = os.path.join ('traces', traceDirName) |
|
931 |
|
932 try: |
|
933 shutil.rmtree(traceDirName) |
|
934 except OSError: |
|
935 pass |
|
936 os.mkdir(traceDirName) |
|
937 |
|
938 #os.system("./waf --cwd regression/traces --run " + |
|
939 # testName + " > /dev/null 2>&1") |
|
940 |
|
941 if pyscript is None: |
|
942 Params.g_options.cwd_launch = traceDirName |
|
943 run_program(testName, command_template=get_command_template(*arguments)) |
|
944 else: |
|
945 argv = [self.env['PYTHON'], os.path.join('..', '..', '..', *os.path.split(pyscript))] + arguments |
|
946 before = os.getcwd() |
|
947 os.chdir(traceDirName) |
|
948 try: |
|
949 _run_argv(argv) |
|
950 finally: |
|
951 os.chdir(before) |
|
952 |
|
953 if verbose: |
|
954 #diffCmd = "diff traces " + refTestDirName + " | head" |
|
955 diffCmd = subprocess.Popen([self.env['DIFF'], traceDirName, refTestDirName], |
|
956 stdout=subprocess.PIPE) |
|
957 headCmd = subprocess.Popen("head", stdin=diffCmd.stdout) |
|
958 rc2 = headCmd.wait() |
|
959 diffCmd.stdout.close() |
|
960 rc1 = diffCmd.wait() |
|
961 rc = rc1 or rc2 |
|
962 else: |
|
963 rc = subprocess.Popen([self.env['DIFF'], traceDirName, refTestDirName], stdout=dev_null()).wait() |
|
964 if rc: |
|
965 print "----------" |
|
966 print "Traces differ in test: test-" + testName |
|
967 print "Reference traces in directory: regression/" + refTestDirName |
|
968 print "Traces in directory: traces" |
|
969 print "Rerun regression test as: " + \ |
|
970 "\"./waf --regression --regression-tests=test-" + testName + "\"" |
|
971 print "Then do \"diff -u regression/" + refTestDirName + " regression/" + traceDirName +\ |
|
972 "\" for details" |
|
973 print "----------" |
|
974 return rc |
|
975 |
|
976 def _find_tests(testdir): |
|
977 """Return a list of test modules in the test directory |
|
978 |
|
979 Arguments: |
|
980 testdir -- the directory to look in for tests |
|
981 """ |
|
982 names = os.listdir(testdir) |
|
983 tests = [] |
|
984 for name in names: |
|
985 if name[:5] == "test-" and name[-3:] == ".py": |
|
986 testname = name[:-3] |
|
987 tests.append(testname) |
|
988 tests.sort() |
|
989 return tests |
|
990 |
|
991 def run_regression(): |
|
992 """Execute regression tests.""" |
|
993 |
|
994 testdir = "tests" |
|
995 if not os.path.exists(testdir): |
|
996 print "Tests directory does not exist" |
|
997 sys.exit(3) |
|
998 |
|
999 sys.path.append(testdir) |
|
1000 sys.modules['tracediff'] = Regression(testdir) |
|
1001 |
|
1002 if Params.g_options.regression_tests: |
|
1003 tests = Params.g_options.regression_tests.split(',') |
|
1004 else: |
|
1005 tests = _find_tests(testdir) |
|
1006 |
|
1007 print "========== Running Regression Tests ==========" |
|
1008 dir_name = APPNAME + '-' + VERSION + REGRESSION_SUFFIX |
|
1009 env = Params.g_build.env_of_name('default') |
|
1010 if env['MERCURIAL']: |
|
1011 print "Synchronizing reference traces using Mercurial." |
|
1012 if not os.path.exists(dir_name): |
|
1013 print "Cloning " + REGRESSION_TRACES_REPO + dir_name + " from repo." |
|
1014 argv = ["hg", "clone", REGRESSION_TRACES_REPO + dir_name, dir_name] |
|
1015 rv = subprocess.Popen(argv).wait() |
|
1016 else: |
|
1017 _dir = os.getcwd() |
|
1018 os.chdir(dir_name) |
|
1019 try: |
|
1020 print "Pulling " + REGRESSION_TRACES_REPO + dir_name + " from repo." |
|
1021 result = subprocess.Popen(["hg", "-q", "pull", REGRESSION_TRACES_REPO + dir_name]).wait() |
|
1022 if not result: |
|
1023 result = subprocess.Popen(["hg", "-q", "update"]).wait() |
|
1024 finally: |
|
1025 os.chdir("..") |
|
1026 if result: |
|
1027 Params.fatal("Synchronizing reference traces using Mercurial failed.") |
|
1028 else: |
|
1029 if not os.path.exists(dir_name): |
|
1030 traceball = dir_name + TRACEBALL_SUFFIX |
|
1031 print "Retrieving " + traceball + " from web." |
|
1032 urllib.urlretrieve(REGRESSION_TRACES_URL + traceball, traceball) |
|
1033 os.system("tar -xjf %s -C .." % (traceball)) |
|
1034 print "Done." |
|
1035 |
|
1036 if not os.path.exists(dir_name): |
|
1037 print "Reference traces directory (%s) does not exist" % dir_name |
|
1038 return 3 |
|
1039 |
|
1040 bad = [] |
|
1041 |
|
1042 for test in tests: |
|
1043 try: |
|
1044 result = _run_regression_test(test) |
|
1045 if result == 0: |
|
1046 if Params.g_options.regression_generate: |
|
1047 print "GENERATE " + test |
|
1048 else: |
|
1049 print "PASS " + test |
|
1050 else: |
|
1051 bad.append(test) |
|
1052 print "FAIL " + test |
|
1053 except NotImplementedError: |
|
1054 print "SKIP " + test |
|
1055 |
|
1056 return len(bad) > 0 |
|
1057 |
|
1058 |
|
1059 def _run_regression_test(test): |
|
1060 """Run a single test. |
|
1061 |
|
1062 Arguments: |
|
1063 test -- the name of the test |
|
1064 """ |
|
1065 |
|
1066 if os.path.exists("traces"): |
|
1067 files = os.listdir("traces") |
|
1068 for file in files: |
|
1069 if file == '.' or file == '..': |
|
1070 continue |
|
1071 shutil.rmtree(os.path.join ("traces", file)) |
|
1072 else: |
|
1073 os.mkdir("traces") |
|
1074 |
|
1075 dir_name = APPNAME + '-' + VERSION + REGRESSION_SUFFIX |
|
1076 |
|
1077 mod = __import__(test, globals(), locals(), []) |
|
1078 return mod.run(verbose=(Params.g_options.verbose > 0), |
|
1079 generate=Params.g_options.regression_generate, |
|
1080 refDirName=dir_name) |
|