4 # |
4 # |
5 # Copyright 2006 Matt Mackall <mpm@selenic.com> |
5 # Copyright 2006 Matt Mackall <mpm@selenic.com> |
6 # |
6 # |
7 # This software may be used and distributed according to the terms of the |
7 # This software may be used and distributed according to the terms of the |
8 # GNU General Public License version 2, incorporated herein by reference. |
8 # GNU General Public License version 2, incorporated herein by reference. |
|
9 |
|
10 # Modifying this script is tricky because it has many modes: |
|
11 # - serial (default) vs parallel (-jN, N > 1) |
|
12 # - no coverage (default) vs coverage (-c, -C, -s) |
|
13 # - temp install (default) vs specific hg script (--with-hg, --local) |
|
14 # - tests are a mix of shell scripts and Python scripts |
|
15 # |
|
16 # If you change this script, it is recommended that you ensure you |
|
17 # haven't broken it by running it in various modes with a representative |
|
18 # sample of test scripts. For example: |
|
19 # |
|
20 # 1) serial, no coverage, temp install: |
|
21 # ./run-tests.py test-s* |
|
22 # 2) serial, no coverage, local hg: |
|
23 # ./run-tests.py --local test-s* |
|
24 # 3) serial, coverage, temp install: |
|
25 # ./run-tests.py -c test-s* |
|
26 # 4) serial, coverage, local hg: |
|
27 # ./run-tests.py -c --local test-s* # unsupported |
|
28 # 5) parallel, no coverage, temp install: |
|
29 # ./run-tests.py -j2 test-s* |
|
30 # 6) parallel, no coverage, local hg: |
|
31 # ./run-tests.py -j2 --local test-s* |
|
32 # 7) parallel, coverage, temp install: |
|
33 # ./run-tests.py -j2 -c test-s* # currently broken |
|
34 # 8) parallel, coverage, local install |
|
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken) |
|
36 # |
|
37 # (You could use any subset of the tests: test-s* happens to match |
|
38 # enough that it's worth doing parallel runs, few enough that it |
|
39 # completes fairly quickly, includes both shell and Python scripts, and |
|
40 # includes some scripts that run daemon processes.) |
9 |
41 |
10 import difflib |
42 import difflib |
11 import errno |
43 import errno |
12 import optparse |
44 import optparse |
13 import os |
45 import os |
32 # reserved exit code to skip test (used by hghave) |
64 # reserved exit code to skip test (used by hghave) |
33 SKIPPED_STATUS = 80 |
65 SKIPPED_STATUS = 80 |
34 SKIPPED_PREFIX = 'skipped: ' |
66 SKIPPED_PREFIX = 'skipped: ' |
35 FAILED_PREFIX = 'hghave check failed: ' |
67 FAILED_PREFIX = 'hghave check failed: ' |
36 PYTHON = sys.executable |
68 PYTHON = sys.executable |
37 hgpkg = None |
|
38 |
69 |
39 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"] |
70 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"] |
40 |
71 |
41 defaults = { |
72 defaults = { |
42 'jobs': ('HGTEST_JOBS', 1), |
73 'jobs': ('HGTEST_JOBS', 1), |
79 parser.add_option("-v", "--verbose", action="store_true", |
110 parser.add_option("-v", "--verbose", action="store_true", |
80 help="output verbose messages") |
111 help="output verbose messages") |
81 parser.add_option("-n", "--nodiff", action="store_true", |
112 parser.add_option("-n", "--nodiff", action="store_true", |
82 help="skip showing test changes") |
113 help="skip showing test changes") |
83 parser.add_option("--with-hg", type="string", |
114 parser.add_option("--with-hg", type="string", |
84 help="test existing install at given location") |
115 metavar="HG", |
|
116 help="test using specified hg script rather than a " |
|
117 "temporary installation") |
|
118 parser.add_option("--local", action="store_true", |
|
119 help="shortcut for --with-hg=<testdir>/../hg") |
85 parser.add_option("--pure", action="store_true", |
120 parser.add_option("--pure", action="store_true", |
86 help="use pure Python code instead of C extensions") |
121 help="use pure Python code instead of C extensions") |
87 |
122 |
88 for option, default in defaults.items(): |
123 for option, default in defaults.items(): |
89 defaults[option] = int(os.environ.get(*default)) |
124 defaults[option] = int(os.environ.get(*default)) |
90 parser.set_defaults(**defaults) |
125 parser.set_defaults(**defaults) |
91 (options, args) = parser.parse_args() |
126 (options, args) = parser.parse_args() |
92 |
127 |
93 global vlog |
128 if options.with_hg: |
|
129 if not (os.path.isfile(options.with_hg) and |
|
130 os.access(options.with_hg, os.X_OK)): |
|
131 parser.error('--with-hg must specify an executable hg script') |
|
132 if not os.path.basename(options.with_hg) == 'hg': |
|
133 sys.stderr.write('warning: --with-hg should specify an hg script') |
|
134 if options.local: |
|
135 testdir = os.path.dirname(os.path.realpath(sys.argv[0])) |
|
136 hgbin = os.path.join(os.path.dirname(testdir), 'hg') |
|
137 if not os.access(hgbin, os.X_OK): |
|
138 parser.error('--local specified, but %r not found or not executable' |
|
139 % hgbin) |
|
140 options.with_hg = hgbin |
|
141 |
94 options.anycoverage = (options.cover or |
142 options.anycoverage = (options.cover or |
95 options.cover_stdlib or |
143 options.cover_stdlib or |
96 options.annotate) |
144 options.annotate) |
97 |
145 |
|
146 if options.anycoverage and options.with_hg: |
|
147 # I'm not sure if this is a fundamental limitation or just a |
|
148 # bug. But I don't want to waste people's time and energy doing |
|
149 # test runs that don't give the results they want. |
|
150 parser.error("sorry, coverage options do not work when --with-hg " |
|
151 "or --local specified") |
|
152 |
|
153 global vlog |
98 if options.verbose: |
154 if options.verbose: |
99 if options.jobs > 1 or options.child is not None: |
155 if options.jobs > 1 or options.child is not None: |
100 pid = "[%d]" % os.getpid() |
156 pid = "[%d]" % os.getpid() |
101 else: |
157 else: |
102 pid = None |
158 pid = None |
510 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' |
556 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' |
511 ' (expected %s)\n' |
557 ' (expected %s)\n' |
512 % (verb, actualhg, expecthg)) |
558 % (verb, actualhg, expecthg)) |
513 |
559 |
514 def runchildren(options, tests): |
560 def runchildren(options, tests): |
515 if not options.with_hg: |
561 if INST: |
516 installhg(options) |
562 installhg(options) |
517 _checkhglib("Testing") |
563 _checkhglib("Testing") |
518 |
564 |
519 optcopy = dict(options.__dict__) |
565 optcopy = dict(options.__dict__) |
520 optcopy['jobs'] = 1 |
566 optcopy['jobs'] = 1 |
521 optcopy['with_hg'] = INST |
567 if optcopy['with_hg'] is None: |
|
568 optcopy['with_hg'] = os.path.join(BINDIR, "hg") |
522 opts = [] |
569 opts = [] |
523 for opt, value in optcopy.iteritems(): |
570 for opt, value in optcopy.iteritems(): |
524 name = '--' + opt.replace('_', '-') |
571 name = '--' + opt.replace('_', '-') |
525 if value is True: |
572 if value is True: |
526 opts.append(name) |
573 opts.append(name) |
577 global DAEMON_PIDS, HGRCPATH |
624 global DAEMON_PIDS, HGRCPATH |
578 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') |
625 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') |
579 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc') |
626 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc') |
580 |
627 |
581 try: |
628 try: |
582 if not options.with_hg: |
629 if INST: |
583 installhg(options) |
630 installhg(options) |
584 _checkhglib("Testing") |
631 _checkhglib("Testing") |
585 |
632 |
586 if options.timeout > 0: |
633 if options.timeout > 0: |
587 try: |
634 try: |
685 os.environ["HGPORT"] = str(options.port) |
732 os.environ["HGPORT"] = str(options.port) |
686 os.environ["HGPORT1"] = str(options.port + 1) |
733 os.environ["HGPORT1"] = str(options.port + 1) |
687 os.environ["HGPORT2"] = str(options.port + 2) |
734 os.environ["HGPORT2"] = str(options.port + 2) |
688 |
735 |
689 if options.with_hg: |
736 if options.with_hg: |
690 INST = options.with_hg |
737 INST = None |
|
738 BINDIR = os.path.dirname(os.path.realpath(options.with_hg)) |
|
739 |
|
740 # This looks redundant with how Python initializes sys.path from |
|
741 # the location of the script being executed. Needed because the |
|
742 # "hg" specified by --with-hg is not the only Python script |
|
743 # executed in the test suite that needs to import 'mercurial' |
|
744 # ... which means it's not really redundant at all. |
|
745 PYTHONDIR = BINDIR |
691 else: |
746 else: |
692 INST = os.path.join(HGTMP, "install") |
747 INST = os.path.join(HGTMP, "install") |
693 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin") |
748 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin") |
694 PYTHONDIR = os.path.join(INST, "lib", "python") |
749 PYTHONDIR = os.path.join(INST, "lib", "python") |
|
750 |
|
751 os.environ["BINDIR"] = BINDIR |
|
752 os.environ["PYTHON"] = PYTHON |
|
753 |
|
754 if not options.child: |
|
755 path = [BINDIR] + os.environ["PATH"].split(os.pathsep) |
|
756 os.environ["PATH"] = os.pathsep.join(path) |
|
757 |
|
758 # Deliberately override existing PYTHONPATH: do not want success |
|
759 # to depend on what happens to be in caller's environment. |
|
760 os.environ["PYTHONPATH"] = PYTHONDIR |
|
761 |
695 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") |
762 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") |
696 |
763 |
697 if len(args) == 0: |
764 if len(args) == 0: |
698 args = os.listdir(".") |
765 args = os.listdir(".") |
699 args.sort() |
766 args.sort() |
708 print "# Ran 0 tests, 0 skipped, 0 failed." |
775 print "# Ran 0 tests, 0 skipped, 0 failed." |
709 return |
776 return |
710 |
777 |
711 vlog("# Using TESTDIR", TESTDIR) |
778 vlog("# Using TESTDIR", TESTDIR) |
712 vlog("# Using HGTMP", HGTMP) |
779 vlog("# Using HGTMP", HGTMP) |
|
780 vlog("# Using PATH", os.environ["PATH"]) |
|
781 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"]) |
713 |
782 |
714 try: |
783 try: |
715 if len(tests) > 1 and options.jobs > 1: |
784 if len(tests) > 1 and options.jobs > 1: |
716 runchildren(options, tests) |
785 runchildren(options, tests) |
717 else: |
786 else: |