/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/__init__.py

  • Committer: Robert Collins
  • Date: 2009-03-27 09:31:43 UTC
  • mto: This revision was merged to the branch mainline in revision 4210.
  • Revision ID: robertc@robertcollins.net-20090327093143-gli7efprtwutx0p4
Create fork and reinvoke parallel testing support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
import doctest
34
34
import errno
35
35
import logging
 
36
import math
36
37
import os
37
38
from pprint import pformat
38
39
import random
39
40
import re
40
41
import shlex
41
42
import stat
42
 
from subprocess import Popen, PIPE
 
43
from subprocess import Popen, PIPE, STDOUT
43
44
import sys
44
45
import tempfile
45
46
import threading
543
544
                run += 1
544
545
            actionTaken = "Listed"
545
546
        else:
546
 
            test.run(result)
 
547
            try:
 
548
                from testtools import ThreadsafeForwardingResult
 
549
            except ImportError:
 
550
                test.run(result)
 
551
            else:
 
552
                if type(result) == ThreadsafeForwardingResult:
 
553
                    test.run(BZRTransformingResult(result))
 
554
                else:
 
555
                    test.run(result)
547
556
            run = result.testsRun
548
557
            actionTaken = "Ran"
549
558
        stopTime = time.time()
2227
2236
        For TestCaseInTempDir we create a temporary directory based on the test
2228
2237
        name and then create two subdirs - test and home under it.
2229
2238
        """
2230
 
        name_prefix = osutils.pathjoin(self.TEST_ROOT, self._getTestDirPrefix())
 
2239
        name_prefix = osutils.pathjoin(TestCaseWithMemoryTransport.TEST_ROOT,
 
2240
            self._getTestDirPrefix())
2231
2241
        name = name_prefix
2232
2242
        for i in range(100):
2233
2243
            if os.path.exists(name):
2251
2261
        self.addCleanup(self.deleteTestDir)
2252
2262
 
2253
2263
    def deleteTestDir(self):
2254
 
        os.chdir(self.TEST_ROOT)
 
2264
        os.chdir(TestCaseWithMemoryTransport.TEST_ROOT)
2255
2265
        _rmtree_temp_dir(self.test_base_dir)
2256
2266
 
2257
2267
    def build_tree(self, shape, line_endings='binary', transport=None):
2671
2681
        return result.wasSuccessful()
2672
2682
 
2673
2683
 
 
2684
# A registry where get() returns a suite decorator.
 
2685
parallel_registry = registry.Registry()
 
2686
def fork_decorator(suite):
 
2687
    concurrency = local_concurrency()
 
2688
    if concurrency == 1:
 
2689
        return suite
 
2690
    from testtools import ConcurrentTestSuite
 
2691
    return ConcurrentTestSuite(suite, fork_for_tests)
 
2692
parallel_registry.register('fork', fork_decorator)
 
2693
def subprocess_decorator(suite):
 
2694
    concurrency = local_concurrency()
 
2695
    if concurrency == 1:
 
2696
        return suite
 
2697
    from testtools import ConcurrentTestSuite
 
2698
    return ConcurrentTestSuite(suite, reinvoke_for_tests)
 
2699
parallel_registry.register('subprocess', subprocess_decorator)
 
2700
 
 
2701
 
2674
2702
def exclude_tests(exclude_pattern):
2675
2703
    """Return a test suite decorator that excludes tests."""
2676
2704
    if exclude_pattern is None:
2836
2864
        return iter(self._tests)
2837
2865
 
2838
2866
 
 
2867
def partition_tests(suite, count):
 
2868
    """Partition suite into count lists of tests."""
 
2869
    result = []
 
2870
    tests = list(iter_suite_tests(suite))
 
2871
    tests_per_process = int(math.ceil(float(len(tests)) / count))
 
2872
    for block in range(count):
 
2873
        low_test = block * tests_per_process
 
2874
        high_test = low_test + tests_per_process
 
2875
        process_tests = tests[low_test:high_test]
 
2876
        result.append(process_tests)
 
2877
    return result
 
2878
 
 
2879
 
 
2880
def fork_for_tests(suite):
 
2881
    """Take suite and start up one runner per CPU by forking()
 
2882
 
 
2883
    :return: An iterable of TestCase-like objects which can each have
 
2884
        run(result) called on them to feed tests to result, and
 
2885
        cleanup() called on them to stop them/kill children/end threads.
 
2886
    """
 
2887
    concurrency = local_concurrency()
 
2888
    result = []
 
2889
    from subunit import TestProtocolClient, ProtocolTestCase
 
2890
    class TestInOtherProcess(ProtocolTestCase):
 
2891
        # Should be in subunit, I think. RBC.
 
2892
        def __init__(self, stream, pid):
 
2893
            ProtocolTestCase.__init__(self, stream)
 
2894
            self.pid = pid
 
2895
 
 
2896
        def run(self, result):
 
2897
            try:
 
2898
                ProtocolTestCase.run(self, result)
 
2899
            finally:
 
2900
                os.waitpid(self.pid, os.WNOHANG)
 
2901
            # print "pid %d finished" % finished_process
 
2902
 
 
2903
    test_blocks = partition_tests(suite, concurrency)
 
2904
    for process_tests in test_blocks:
 
2905
        process_suite = TestSuite()
 
2906
        process_suite.addTests(process_tests)
 
2907
        c2pread, c2pwrite = os.pipe()
 
2908
        pid = os.fork()
 
2909
        if pid == 0:
 
2910
            try:
 
2911
                os.close(c2pread)
 
2912
                # Leave stderr and stdout open so we can see test noise
 
2913
                # Close stdin so that the child goes away if it decides to
 
2914
                # read from stdin (otherwise its a roulette to see what
 
2915
                # child actually gets keystrokes for pdb etc.
 
2916
                sys.stdin.close()
 
2917
                sys.stdin = None
 
2918
                stream = os.fdopen(c2pwrite, 'wb', 0)
 
2919
                subunit_result = TestProtocolClient(stream)
 
2920
                process_suite.run(subunit_result)
 
2921
            finally:
 
2922
                os._exit(0)
 
2923
        else:
 
2924
            os.close(c2pwrite)
 
2925
            stream = os.fdopen(c2pread, 'rb', 1)
 
2926
            test = TestInOtherProcess(stream, pid)
 
2927
            result.append(test)
 
2928
    return result
 
2929
 
 
2930
 
 
2931
def reinvoke_for_tests(suite):
 
2932
    """Take suite and start up one runner per CPU using subprocess().
 
2933
 
 
2934
    :return: An iterable of TestCase-like objects which can each have
 
2935
        run(result) called on them to feed tests to result, and
 
2936
        cleanup() called on them to stop them/kill children/end threads.
 
2937
    """
 
2938
    concurrency = local_concurrency()
 
2939
    result = []
 
2940
    from subunit import TestProtocolClient, ProtocolTestCase
 
2941
    class TestInSubprocess(ProtocolTestCase):
 
2942
        def __init__(self, process, name):
 
2943
            ProtocolTestCase.__init__(self, process.stdout)
 
2944
            self.process = process
 
2945
            self.process.stdin.close()
 
2946
            self.name = name
 
2947
 
 
2948
        def run(self, result):
 
2949
            try:
 
2950
                ProtocolTestCase.run(self, result)
 
2951
            finally:
 
2952
                self.process.wait()
 
2953
                os.unlink(self.name)
 
2954
            # print "pid %d finished" % finished_process
 
2955
    test_blocks = partition_tests(suite, concurrency)
 
2956
    for process_tests in test_blocks:
 
2957
        # ugly; currently reimplement rather than reuses TestCase methods.
 
2958
        bzr_path = os.path.dirname(os.path.dirname(bzrlib.__file__))+'/bzr'
 
2959
        if not os.path.isfile(bzr_path):
 
2960
            # We are probably installed. Assume sys.argv is the right file
 
2961
            bzr_path = sys.argv[0]
 
2962
        fd, test_list_file_name = tempfile.mkstemp()
 
2963
        test_list_file = os.fdopen(fd, 'wb', 1)
 
2964
        for test in process_tests:
 
2965
            test_list_file.write(test.id() + '\n')
 
2966
        test_list_file.close()
 
2967
        try:
 
2968
            argv = [bzr_path, 'selftest', '--load-list', test_list_file_name,
 
2969
                '--subunit']
 
2970
            # Note that without subunit in core - epic FAIL. But we don't really
 
2971
            # care anyway because load-list means we don't get plugin tests.
 
2972
            if '--no-plugins' in sys.argv:
 
2973
                argv.append('--no-plugins')
 
2974
            # stderr=STDOUT would be ideal, but until we prevent noise on
 
2975
            # stderr it can interrupt the subunit protocol.
 
2976
            process = Popen(argv, stdin=PIPE, stdout=PIPE, stderr=PIPE,
 
2977
                bufsize=1)
 
2978
            test = TestInSubprocess(process, test_list_file_name)
 
2979
            result.append(test)
 
2980
        except:
 
2981
            os.unlink(test_list_file_name)
 
2982
            raise
 
2983
    return result
 
2984
 
 
2985
 
 
2986
def cpucount(content):
 
2987
    lines = content.splitlines()
 
2988
    prefix = 'processor'
 
2989
    for line in lines:
 
2990
        if line.startswith(prefix):
 
2991
            concurrency = int(line[line.find(':')+1:]) + 1
 
2992
    return concurrency
 
2993
 
 
2994
 
 
2995
def local_concurrency():
 
2996
    try:
 
2997
        content = file('/proc/cpuinfo', 'rb').read()
 
2998
        concurrency = cpucount(content)
 
2999
    except Exception, e:
 
3000
        concurrency = 1
 
3001
    return concurrency
 
3002
 
 
3003
 
 
3004
class BZRTransformingResult(unittest.TestResult):
 
3005
 
 
3006
    def __init__(self, target):
 
3007
        unittest.TestResult.__init__(self)
 
3008
        self.result = target
 
3009
 
 
3010
    def startTest(self, test):
 
3011
        self.result.startTest(test)
 
3012
 
 
3013
    def stopTest(self, test):
 
3014
        self.result.stopTest(test)
 
3015
 
 
3016
    def addError(self, test, err):
 
3017
        feature = self._error_looks_like('UnavailableFeature: ', err)
 
3018
        if feature is not None:
 
3019
            self.result.addNotSupported(test, feature)
 
3020
        else:
 
3021
            self.result.addError(test, err)
 
3022
 
 
3023
    def addFailure(self, test, err):
 
3024
        known = self._error_looks_like('KnownFailure: ', err)
 
3025
        if known is not None:
 
3026
            self.result._addKnownFailure(test, [KnownFailure,
 
3027
                                                KnownFailure(known), None])
 
3028
        else:
 
3029
            self.result.addFailure(test, err)
 
3030
 
 
3031
    def addSkip(self, test, reason):
 
3032
        self.result.addSkip(test, reason)
 
3033
 
 
3034
    def addSuccess(self, test):
 
3035
        self.result.addSuccess(test)
 
3036
 
 
3037
    def _error_looks_like(self, prefix, err):
 
3038
        """Deserialize exception and returns the stringify value."""
 
3039
        import subunit
 
3040
        value = None
 
3041
        typ, exc, _ = err
 
3042
        if isinstance(exc, subunit.RemoteException):
 
3043
            # stringify the exception gives access to the remote traceback
 
3044
            # We search the last line for 'prefix'
 
3045
            lines = str(exc).split('\n')
 
3046
            if len(lines) > 1:
 
3047
                last = lines[-2] # -1 is empty, final \n
 
3048
            else:
 
3049
                last = lines[-1]
 
3050
            if last.startswith(prefix):
 
3051
                value = last[len(prefix):]
 
3052
        return value
 
3053
 
 
3054
 
2839
3055
# Controlled by "bzr selftest -E=..." option
2840
3056
selftest_debug_flags = set()
2841
3057
 
2854
3070
             debug_flags=None,
2855
3071
             starting_with=None,
2856
3072
             runner_class=None,
 
3073
             suite_decorators=None,
2857
3074
             ):
2858
3075
    """Run the whole test suite under the enhanced runner"""
2859
3076
    # XXX: Very ugly way to do this...
2891
3108
                     exclude_pattern=exclude_pattern,
2892
3109
                     strict=strict,
2893
3110
                     runner_class=runner_class,
 
3111
                     suite_decorators=suite_decorators,
2894
3112
                     )
2895
3113
    finally:
2896
3114
        default_transport = old_transport