51 import signal |
51 import signal |
52 import sys |
52 import sys |
53 import tempfile |
53 import tempfile |
54 import time |
54 import time |
55 import re |
55 import re |
|
56 import threading |
56 |
57 |
57 closefds = os.name == 'posix' |
58 closefds = os.name == 'posix' |
58 def Popen4(cmd, bufsize=-1): |
59 def Popen4(cmd, timeout): |
59 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize, |
60 p = subprocess.Popen(cmd, shell=True, bufsize=-1, |
60 close_fds=closefds, |
61 close_fds=closefds, |
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE, |
62 stdin=subprocess.PIPE, stdout=subprocess.PIPE, |
62 stderr=subprocess.STDOUT) |
63 stderr=subprocess.STDOUT) |
63 p.fromchild = p.stdout |
64 p.fromchild = p.stdout |
64 p.tochild = p.stdin |
65 p.tochild = p.stdin |
65 p.childerr = p.stderr |
66 p.childerr = p.stderr |
|
67 |
|
68 if timeout: |
|
69 p.timeout = False |
|
70 def t(): |
|
71 start = time.time() |
|
72 while time.time() - start < timeout and p.returncode is None: |
|
73 time.sleep(1) |
|
74 p.timeout = True |
|
75 if p.returncode is None: |
|
76 try: |
|
77 p.terminate() |
|
78 except OSError: |
|
79 pass |
|
80 threading.Thread(target=t).start() |
|
81 |
66 return p |
82 return p |
67 |
83 |
68 # reserved exit code to skip test (used by hghave) |
84 # reserved exit code to skip test (used by hghave) |
69 SKIPPED_STATUS = 80 |
85 SKIPPED_STATUS = 80 |
70 SKIPPED_PREFIX = 'skipped: ' |
86 SKIPPED_PREFIX = 'skipped: ' |
437 adir = os.path.join(TESTDIR, 'annotated') |
453 adir = os.path.join(TESTDIR, 'annotated') |
438 if not os.path.isdir(adir): |
454 if not os.path.isdir(adir): |
439 os.mkdir(adir) |
455 os.mkdir(adir) |
440 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit) |
456 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit) |
441 |
457 |
442 class Timeout(Exception): |
|
443 pass |
|
444 |
|
445 def alarmed(signum, frame): |
|
446 raise Timeout |
|
447 |
|
448 def pytest(test, options, replacements): |
458 def pytest(test, options, replacements): |
449 py3kswitch = options.py3k_warnings and ' -3' or '' |
459 py3kswitch = options.py3k_warnings and ' -3' or '' |
450 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test) |
460 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test) |
451 vlog("# Running", cmd) |
461 vlog("# Running", cmd) |
452 return run(cmd, options, replacements) |
462 return run(cmd, options, replacements) |
600 output = fromchild.read() |
610 output = fromchild.read() |
601 ret = fromchild.close() |
611 ret = fromchild.close() |
602 if ret is None: |
612 if ret is None: |
603 ret = 0 |
613 ret = 0 |
604 else: |
614 else: |
605 proc = Popen4(cmd) |
615 proc = Popen4(cmd, options.timeout) |
606 def cleanup(): |
616 def cleanup(): |
607 os.kill(proc.pid, signal.SIGTERM) |
617 try: |
|
618 proc.terminate() |
|
619 except OSError: |
|
620 pass |
608 ret = proc.wait() |
621 ret = proc.wait() |
609 if ret == 0: |
622 if ret == 0: |
610 ret = signal.SIGTERM << 8 |
623 ret = signal.SIGTERM << 8 |
611 killdaemons() |
624 killdaemons() |
612 return ret |
625 return ret |
613 |
626 |
|
627 output = '' |
|
628 proc.tochild.close() |
|
629 |
614 try: |
630 try: |
615 output = '' |
|
616 proc.tochild.close() |
|
617 output = proc.fromchild.read() |
631 output = proc.fromchild.read() |
618 ret = proc.wait() |
|
619 if wifexited(ret): |
|
620 ret = os.WEXITSTATUS(ret) |
|
621 except Timeout: |
|
622 vlog('# Process %d timed out - killing it' % proc.pid) |
|
623 cleanup() |
|
624 ret = 'timeout' |
|
625 output += ("\n### Abort: timeout after %d seconds.\n" |
|
626 % options.timeout) |
|
627 except KeyboardInterrupt: |
632 except KeyboardInterrupt: |
628 vlog('# Handling keyboard interrupt') |
633 vlog('# Handling keyboard interrupt') |
629 cleanup() |
634 cleanup() |
630 raise |
635 raise |
631 |
636 |
|
637 ret = proc.wait() |
|
638 if wifexited(ret): |
|
639 ret = os.WEXITSTATUS(ret) |
|
640 |
|
641 if proc.timeout: |
|
642 ret = 'timeout' |
|
643 |
|
644 if ret: |
|
645 killdaemons() |
|
646 |
632 for s, r in replacements: |
647 for s, r in replacements: |
633 output = re.sub(s, r, output) |
648 output = re.sub(s, r, output) |
634 return ret, splitnewlines(output) |
649 return ret, splitnewlines(output) |
635 |
650 |
636 def runone(options, test, results): |
651 def runone(options, test): |
637 '''tristate output: |
652 '''tristate output: |
638 None -> skipped |
653 None -> skipped |
639 True -> passed |
654 True -> passed |
640 False -> failed''' |
655 False -> failed''' |
641 |
656 |
|
657 global results, resultslock, iolock |
|
658 |
642 testpath = os.path.join(TESTDIR, test) |
659 testpath = os.path.join(TESTDIR, test) |
|
660 |
|
661 def result(l, e): |
|
662 resultslock.acquire() |
|
663 results[l].append(e) |
|
664 resultslock.release() |
643 |
665 |
644 def skip(msg): |
666 def skip(msg): |
645 if not options.verbose: |
667 if not options.verbose: |
646 results['s'].append((test, msg)) |
668 result('s', (test, msg)) |
647 else: |
669 else: |
|
670 iolock.acquire() |
648 print "\nSkipping %s: %s" % (testpath, msg) |
671 print "\nSkipping %s: %s" % (testpath, msg) |
|
672 iolock.release() |
649 return None |
673 return None |
650 |
674 |
651 def fail(msg, ret): |
675 def fail(msg, ret): |
652 if not options.nodiff: |
676 if not options.nodiff: |
|
677 iolock.acquire() |
653 print "\nERROR: %s %s" % (testpath, msg) |
678 print "\nERROR: %s %s" % (testpath, msg) |
|
679 iolock.release() |
654 if (not ret and options.interactive |
680 if (not ret and options.interactive |
655 and os.path.exists(testpath + ".err")): |
681 and os.path.exists(testpath + ".err")): |
|
682 iolock.acquire() |
656 print "Accept this change? [n] ", |
683 print "Accept this change? [n] ", |
657 answer = sys.stdin.readline().strip() |
684 answer = sys.stdin.readline().strip() |
|
685 iolock.release() |
658 if answer.lower() in "y yes".split(): |
686 if answer.lower() in "y yes".split(): |
659 if test.endswith(".t"): |
687 if test.endswith(".t"): |
660 rename(testpath + ".err", testpath) |
688 rename(testpath + ".err", testpath) |
661 else: |
689 else: |
662 rename(testpath + ".err", testpath + ".out") |
690 rename(testpath + ".err", testpath + ".out") |
663 return |
691 return |
664 results['f'].append((test, msg)) |
692 result('f', (test, msg)) |
665 |
693 |
666 def success(): |
694 def success(): |
667 results['p'].append(test) |
695 result('p', test) |
668 |
696 |
669 def ignore(msg): |
697 def ignore(msg): |
670 results['i'].append((test, msg)) |
698 result('i', (test, msg)) |
671 |
699 |
672 if (test.startswith("test-") and '~' not in test and |
700 if (test.startswith("test-") and '~' not in test and |
673 ('.' not in test or test.endswith('.py') or |
701 ('.' not in test or test.endswith('.py') or |
674 test.endswith('.bat') or test.endswith('.t'))): |
702 test.endswith('.bat') or test.endswith('.t'))): |
675 if not os.path.exists(test): |
703 if not os.path.exists(test): |
745 os.path.join(HGTMP, test) |
773 os.path.join(HGTMP, test) |
746 |
774 |
747 os.mkdir(testtmp) |
775 os.mkdir(testtmp) |
748 os.chdir(testtmp) |
776 os.chdir(testtmp) |
749 |
777 |
750 if options.timeout > 0: |
|
751 signal.alarm(options.timeout) |
|
752 |
|
753 ret, out = runner(testpath, options, [ |
778 ret, out = runner(testpath, options, [ |
754 (re.escape(testtmp), '$TESTTMP'), |
779 (re.escape(testtmp), '$TESTTMP'), |
755 (r':%s\b' % options.port, ':$HGPORT'), |
780 (r':%s\b' % options.port, ':$HGPORT'), |
756 (r':%s\b' % (options.port + 1), ':$HGPORT1'), |
781 (r':%s\b' % (options.port + 1), ':$HGPORT1'), |
757 (r':%s\b' % (options.port + 2), ':$HGPORT2'), |
782 (r':%s\b' % (options.port + 2), ':$HGPORT2'), |
758 ]) |
783 ]) |
759 vlog("# Ret was:", ret) |
784 vlog("# Ret was:", ret) |
760 |
|
761 if options.timeout > 0: |
|
762 signal.alarm(0) |
|
763 |
785 |
764 mark = '.' |
786 mark = '.' |
765 if ret == 0: |
787 if ret == 0: |
766 success() |
788 success() |
767 |
789 |
797 if failed: |
819 if failed: |
798 fail("hghave failed checking for %s" % failed[-1], ret) |
820 fail("hghave failed checking for %s" % failed[-1], ret) |
799 skipped = False |
821 skipped = False |
800 else: |
822 else: |
801 skip(missing[-1]) |
823 skip(missing[-1]) |
|
824 elif ret == 'timeout': |
|
825 mark = 't' |
|
826 fail("timed out", ret) |
802 elif out != refout: |
827 elif out != refout: |
803 mark = '!' |
828 mark = '!' |
804 if ret == 'timeout': |
829 if not options.nodiff: |
805 fail("timed out", ret) |
830 iolock.acquire() |
806 elif ret: |
|
807 fail("output changed and returned error code %d" % ret, ret) |
|
808 else: |
|
809 fail("output changed", ret) |
|
810 if ret != 'timeout' and not options.nodiff: |
|
811 if options.view: |
831 if options.view: |
812 os.system("%s %s %s" % (options.view, ref, err)) |
832 os.system("%s %s %s" % (options.view, ref, err)) |
813 else: |
833 else: |
814 showdiff(refout, out, ref, err) |
834 showdiff(refout, out, ref, err) |
|
835 iolock.release() |
|
836 if ret: |
|
837 fail("output changed and returned error code %d" % ret, ret) |
|
838 else: |
|
839 fail("output changed", ret) |
815 ret = 1 |
840 ret = 1 |
816 elif ret: |
841 elif ret: |
817 mark = '!' |
842 mark = '!' |
818 fail("returned error code %d" % ret, ret) |
843 fail("returned error code %d" % ret, ret) |
819 |
844 |
820 if not options.verbose: |
845 if not options.verbose: |
|
846 iolock.acquire() |
821 sys.stdout.write(mark) |
847 sys.stdout.write(mark) |
822 sys.stdout.flush() |
848 sys.stdout.flush() |
|
849 iolock.release() |
823 |
850 |
824 killdaemons() |
851 killdaemons() |
825 |
852 |
826 os.chdir(TESTDIR) |
853 os.chdir(TESTDIR) |
827 if not options.keep_tmpdir: |
854 if not options.keep_tmpdir: |
933 |
960 |
934 if options.anycoverage: |
961 if options.anycoverage: |
935 outputcoverage(options) |
962 outputcoverage(options) |
936 sys.exit(failures != 0) |
963 sys.exit(failures != 0) |
937 |
964 |
|
965 results = dict(p=[], f=[], s=[], i=[]) |
|
966 resultslock = threading.Lock() |
|
967 iolock = threading.Lock() |
|
968 |
938 def runqueue(options, tests, results): |
969 def runqueue(options, tests, results): |
939 for test in tests: |
970 for test in tests: |
940 ret = runone(options, test, results) |
971 ret = runone(options, test) |
941 if options.first and ret is not None and not ret: |
972 if options.first and ret is not None and not ret: |
942 break |
973 break |
943 |
974 |
944 def runtests(options, tests): |
975 def runtests(options, tests): |
945 global DAEMON_PIDS, HGRCPATH |
976 global DAEMON_PIDS, HGRCPATH |
946 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') |
977 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') |
947 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc') |
978 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc') |
948 |
979 |
949 results = dict(p=[], f=[], s=[], i=[]) |
|
950 |
|
951 try: |
980 try: |
952 if INST: |
981 if INST: |
953 installhg(options) |
982 installhg(options) |
954 _checkhglib("Testing") |
983 _checkhglib("Testing") |
955 |
|
956 if options.timeout > 0: |
|
957 try: |
|
958 signal.signal(signal.SIGALRM, alarmed) |
|
959 vlog('# Running each test with %d second timeout' % |
|
960 options.timeout) |
|
961 except AttributeError: |
|
962 print 'WARNING: cannot run tests with timeouts' |
|
963 options.timeout = 0 |
|
964 |
984 |
965 if options.restart: |
985 if options.restart: |
966 orig = list(tests) |
986 orig = list(tests) |
967 while tests: |
987 while tests: |
968 if os.path.exists(tests[0] + ".err"): |
988 if os.path.exists(tests[0] + ".err"): |