~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/features.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-05-11 11:47:36 UTC
  • mfrom: (5200.3.8 lock_return)
  • Revision ID: pqm@pqm.ubuntu.com-20100511114736-mc1sq9zyo3vufec7
(lifeless) Provide a consistent interface to Tree, Branch,
 Repository where lock methods return an object with an unlock method to
 unlock the lock. This breaks the API for Branch,
 Repository on their lock_write methods. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009, 2010, 2011 Canonical Ltd
 
1
# Copyright (C) 2009, 2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
"""A collection of commonly used 'Features' to optionally run tests.
18
 
"""
19
 
 
20
17
import os
21
 
import subprocess
22
18
import stat
23
 
import sys
24
 
import tempfile
25
 
 
26
 
from bzrlib import (
27
 
    osutils,
28
 
    symbol_versioning,
29
 
    tests,
30
 
    )
31
 
 
32
 
 
33
 
class Feature(object):
34
 
    """An operating system Feature."""
35
 
 
36
 
    def __init__(self):
37
 
        self._available = None
38
 
 
39
 
    def available(self):
40
 
        """Is the feature available?
41
 
 
42
 
        :return: True if the feature is available.
43
 
        """
44
 
        if self._available is None:
45
 
            self._available = self._probe()
46
 
        return self._available
47
 
 
48
 
    def _probe(self):
49
 
        """Implement this method in concrete features.
50
 
 
51
 
        :return: True if the feature is available.
52
 
        """
53
 
        raise NotImplementedError
54
 
 
55
 
    def __str__(self):
56
 
        if getattr(self, 'feature_name', None):
57
 
            return self.feature_name()
58
 
        return self.__class__.__name__
59
 
 
60
 
 
61
 
class _SymlinkFeature(Feature):
62
 
 
63
 
    def _probe(self):
64
 
        return osutils.has_symlinks()
65
 
 
66
 
    def feature_name(self):
67
 
        return 'symlinks'
68
 
 
69
 
SymlinkFeature = _SymlinkFeature()
70
 
 
71
 
 
72
 
class _HardlinkFeature(Feature):
73
 
 
74
 
    def _probe(self):
75
 
        return osutils.has_hardlinks()
76
 
 
77
 
    def feature_name(self):
78
 
        return 'hardlinks'
79
 
 
80
 
HardlinkFeature = _HardlinkFeature()
81
 
 
82
 
 
83
 
class _OsFifoFeature(Feature):
84
 
 
85
 
    def _probe(self):
86
 
        return getattr(os, 'mkfifo', None)
87
 
 
88
 
    def feature_name(self):
89
 
        return 'filesystem fifos'
90
 
 
91
 
OsFifoFeature = _OsFifoFeature()
92
 
 
93
 
 
94
 
class _UnicodeFilenameFeature(Feature):
95
 
    """Does the filesystem support Unicode filenames?"""
96
 
 
97
 
    def _probe(self):
98
 
        try:
99
 
            # Check for character combinations unlikely to be covered by any
100
 
            # single non-unicode encoding. We use the characters
101
 
            # - greek small letter alpha (U+03B1) and
102
 
            # - braille pattern dots-123456 (U+283F).
103
 
            os.stat(u'\u03b1\u283f')
104
 
        except UnicodeEncodeError:
105
 
            return False
106
 
        except (IOError, OSError):
107
 
            # The filesystem allows the Unicode filename but the file doesn't
108
 
            # exist.
109
 
            return True
110
 
        else:
111
 
            # The filesystem allows the Unicode filename and the file exists,
112
 
            # for some reason.
113
 
            return True
114
 
 
115
 
UnicodeFilenameFeature = _UnicodeFilenameFeature()
116
 
 
117
 
 
118
 
class _CompatabilityThunkFeature(Feature):
119
 
    """This feature is just a thunk to another feature.
120
 
 
121
 
    It issues a deprecation warning if it is accessed, to let you know that you
122
 
    should really use a different feature.
123
 
    """
124
 
 
125
 
    def __init__(self, dep_version, module, name,
126
 
                 replacement_name, replacement_module=None):
127
 
        super(_CompatabilityThunkFeature, self).__init__()
128
 
        self._module = module
129
 
        if replacement_module is None:
130
 
            replacement_module = module
131
 
        self._replacement_module = replacement_module
132
 
        self._name = name
133
 
        self._replacement_name = replacement_name
134
 
        self._dep_version = dep_version
135
 
        self._feature = None
136
 
 
137
 
    def _ensure(self):
138
 
        if self._feature is None:
139
 
            from bzrlib import pyutils
140
 
            depr_msg = self._dep_version % ('%s.%s'
141
 
                                            % (self._module, self._name))
142
 
            use_msg = ' Use %s.%s instead.' % (self._replacement_module,
143
 
                                               self._replacement_name)
144
 
            symbol_versioning.warn(depr_msg + use_msg, DeprecationWarning)
145
 
            # Import the new feature and use it as a replacement for the
146
 
            # deprecated one.
147
 
            self._feature = pyutils.get_named_object(
148
 
                self._replacement_module, self._replacement_name)
149
 
 
150
 
    def _probe(self):
151
 
        self._ensure()
152
 
        return self._feature._probe()
153
 
 
154
 
 
155
 
class ModuleAvailableFeature(Feature):
156
 
    """This is a feature than describes a module we want to be available.
157
 
 
158
 
    Declare the name of the module in __init__(), and then after probing, the
159
 
    module will be available as 'self.module'.
160
 
 
161
 
    :ivar module: The module if it is available, else None.
162
 
    """
163
 
 
164
 
    def __init__(self, module_name):
165
 
        super(ModuleAvailableFeature, self).__init__()
166
 
        self.module_name = module_name
167
 
 
168
 
    def _probe(self):
169
 
        sentinel = object()
170
 
        module = sys.modules.get(self.module_name, sentinel)
171
 
        if module is sentinel:
172
 
            try:
173
 
                self._module = __import__(self.module_name, {}, {}, [''])
174
 
                return True
175
 
            except ImportError:
176
 
                return False
177
 
        else:
178
 
            self._module = module
179
 
            return True
180
 
 
181
 
    @property
182
 
    def module(self):
183
 
        if self.available():
184
 
            return self._module
185
 
        return None
186
 
 
187
 
    def feature_name(self):
188
 
        return self.module_name
189
 
 
190
 
 
191
 
class _HTTPSServerFeature(Feature):
192
 
    """Some tests want an https Server, check if one is available.
193
 
 
194
 
    Right now, the only way this is available is under python2.6 which provides
195
 
    an ssl module.
196
 
    """
197
 
 
198
 
    def _probe(self):
199
 
        try:
200
 
            import ssl
201
 
            return True
202
 
        except ImportError:
203
 
            return False
204
 
 
205
 
    def feature_name(self):
206
 
        return 'HTTPSServer'
207
 
 
208
 
 
209
 
HTTPSServerFeature = _HTTPSServerFeature()
210
 
 
211
 
 
212
 
class _ByteStringNamedFilesystem(Feature):
213
 
    """Is the filesystem based on bytes?"""
214
 
 
215
 
    def _probe(self):
216
 
        if os.name == "posix":
217
 
            return True
218
 
        return False
219
 
 
220
 
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
221
 
 
222
 
 
223
 
class _UTF8Filesystem(Feature):
224
 
    """Is the filesystem UTF-8?"""
225
 
 
226
 
    def _probe(self):
227
 
        if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
228
 
            return True
229
 
        return False
230
 
 
231
 
UTF8Filesystem = _UTF8Filesystem()
232
 
 
233
 
 
234
 
class _BreakinFeature(Feature):
235
 
    """Does this platform support the breakin feature?"""
236
 
 
237
 
    def _probe(self):
238
 
        from bzrlib import breakin
239
 
        if breakin.determine_signal() is None:
240
 
            return False
241
 
        if sys.platform == 'win32':
242
 
            # Windows doesn't have os.kill, and we catch the SIGBREAK signal.
243
 
            # We trigger SIGBREAK via a Console api so we need ctypes to
244
 
            # access the function
245
 
            try:
246
 
                import ctypes
247
 
            except OSError:
248
 
                return False
249
 
        return True
250
 
 
251
 
    def feature_name(self):
252
 
        return "SIGQUIT or SIGBREAK w/ctypes on win32"
253
 
 
254
 
 
255
 
BreakinFeature = _BreakinFeature()
256
 
 
257
 
 
258
 
class _CaseInsCasePresFilenameFeature(Feature):
259
 
    """Is the file-system case insensitive, but case-preserving?"""
260
 
 
261
 
    def _probe(self):
262
 
        fileno, name = tempfile.mkstemp(prefix='MixedCase')
263
 
        try:
264
 
            # first check truly case-preserving for created files, then check
265
 
            # case insensitive when opening existing files.
266
 
            name = osutils.normpath(name)
267
 
            base, rel = osutils.split(name)
268
 
            found_rel = osutils.canonical_relpath(base, name)
269
 
            return (found_rel == rel
270
 
                    and os.path.isfile(name.upper())
271
 
                    and os.path.isfile(name.lower()))
272
 
        finally:
273
 
            os.close(fileno)
274
 
            os.remove(name)
275
 
 
276
 
    def feature_name(self):
277
 
        return "case-insensitive case-preserving filesystem"
278
 
 
279
 
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
280
 
 
281
 
 
282
 
class _CaseInsensitiveFilesystemFeature(Feature):
283
 
    """Check if underlying filesystem is case-insensitive but *not* case
284
 
    preserving.
285
 
    """
286
 
    # Note that on Windows, Cygwin, MacOS etc, the file-systems are far
287
 
    # more likely to be case preserving, so this case is rare.
288
 
 
289
 
    def _probe(self):
290
 
        if CaseInsCasePresFilenameFeature.available():
291
 
            return False
292
 
 
293
 
        if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
294
 
            root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
295
 
            tests.TestCaseWithMemoryTransport.TEST_ROOT = root
296
 
        else:
297
 
            root = tests.TestCaseWithMemoryTransport.TEST_ROOT
298
 
        tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
299
 
            dir=root)
300
 
        name_a = osutils.pathjoin(tdir, 'a')
301
 
        name_A = osutils.pathjoin(tdir, 'A')
302
 
        os.mkdir(name_a)
303
 
        result = osutils.isdir(name_A)
304
 
        tests._rmtree_temp_dir(tdir)
305
 
        return result
306
 
 
307
 
    def feature_name(self):
308
 
        return 'case-insensitive filesystem'
309
 
 
310
 
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
311
 
 
312
 
 
313
 
class _CaseSensitiveFilesystemFeature(Feature):
314
 
 
315
 
    def _probe(self):
316
 
        if CaseInsCasePresFilenameFeature.available():
317
 
            return False
318
 
        elif CaseInsensitiveFilesystemFeature.available():
319
 
            return False
320
 
        else:
321
 
            return True
322
 
 
323
 
    def feature_name(self):
324
 
        return 'case-sensitive filesystem'
325
 
 
326
 
# new coding style is for feature instances to be lowercase
327
 
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
328
 
 
329
 
 
330
 
class _NotRunningAsRoot(Feature):
331
 
 
332
 
    def _probe(self):
333
 
        try:
334
 
            uid = os.getuid()
335
 
        except AttributeError:
336
 
            # If there is no uid, chances are there is no root either
337
 
            return True
338
 
        return uid != 0
339
 
 
340
 
    def feature_name(self):
341
 
        return 'Not running as root'
342
 
 
343
 
 
344
 
not_running_as_root = _NotRunningAsRoot()
345
 
 
346
 
apport = ModuleAvailableFeature('apport')
347
 
gpgme = ModuleAvailableFeature('gpgme')
348
 
lzma = ModuleAvailableFeature('lzma')
349
 
meliae = ModuleAvailableFeature('meliae.scanner')
350
 
paramiko = ModuleAvailableFeature('paramiko')
351
 
pycurl = ModuleAvailableFeature('pycurl')
352
 
pywintypes = ModuleAvailableFeature('pywintypes')
353
 
sphinx = ModuleAvailableFeature('sphinx')
354
 
subunit = ModuleAvailableFeature('subunit')
355
 
testtools = ModuleAvailableFeature('testtools')
356
 
 
357
 
compiled_patiencediff_feature = ModuleAvailableFeature(
358
 
    'bzrlib._patiencediff_c')
359
 
lsprof_feature = ModuleAvailableFeature('bzrlib.lsprof')
360
 
 
361
 
 
362
 
class _BackslashDirSeparatorFeature(Feature):
363
 
 
364
 
    def _probe(self):
365
 
        try:
366
 
            os.lstat(os.getcwd() + '\\')
367
 
        except OSError:
368
 
            return False
369
 
        else:
370
 
            return True
371
 
 
372
 
    def feature_name(self):
373
 
        return "Filesystem treats '\\' as a directory separator."
374
 
 
375
 
backslashdir_feature = _BackslashDirSeparatorFeature()
376
 
 
377
 
 
378
 
class _ChownFeature(Feature):
379
 
    """os.chown is supported"""
380
 
 
381
 
    def _probe(self):
382
 
        return os.name == 'posix' and hasattr(os, 'chown')
383
 
 
384
 
chown_feature = _ChownFeature()
385
 
 
386
 
 
387
 
class ExecutableFeature(Feature):
388
 
    """Feature testing whether an executable of a given name is on the PATH."""
389
 
 
390
 
    def __init__(self, name):
391
 
        super(ExecutableFeature, self).__init__()
392
 
        self.name = name
393
 
        self._path = None
394
 
 
395
 
    @property
396
 
    def path(self):
397
 
        # This is a property, so accessing path ensures _probe was called
398
 
        self.available()
399
 
        return self._path
400
 
 
401
 
    def _probe(self):
402
 
        self._path = osutils.find_executable_on_path(self.name)
403
 
        return self._path is not None
404
 
 
405
 
    def feature_name(self):
406
 
        return '%s executable' % self.name
407
 
 
408
 
 
409
 
bash_feature = ExecutableFeature('bash')
410
 
sed_feature = ExecutableFeature('sed')
411
 
diff_feature = ExecutableFeature('diff')
412
 
 
413
 
 
414
 
class _PosixPermissionsFeature(Feature):
 
19
 
 
20
from bzrlib import tests
 
21
from bzrlib.symbol_versioning import deprecated_in
 
22
 
 
23
 
 
24
apport = tests.ModuleAvailableFeature('apport')
 
25
paramiko = tests.ModuleAvailableFeature('paramiko')
 
26
pycurl = tests.ModuleAvailableFeature('pycurl')
 
27
subunit = tests.ModuleAvailableFeature('subunit')
 
28
 
 
29
 
 
30
class _PosixPermissionsFeature(tests.Feature):
415
31
 
416
32
    def _probe(self):
417
33
        def has_perms():
418
 
            # Create temporary file and check if specified perms are
419
 
            # maintained.
 
34
            # create temporary file and check if specified perms are maintained.
 
35
            import tempfile
 
36
 
420
37
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
421
38
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
422
39
            fd, name = f
436
53
posix_permissions_feature = _PosixPermissionsFeature()
437
54
 
438
55
 
439
 
class _StraceFeature(Feature):
440
 
 
441
 
    def _probe(self):
442
 
        try:
443
 
            proc = subprocess.Popen(['strace'],
444
 
                stderr=subprocess.PIPE,
445
 
                stdout=subprocess.PIPE)
446
 
            proc.communicate()
447
 
            return True
448
 
        except OSError, e:
449
 
            if e.errno == errno.ENOENT:
450
 
                # strace is not installed
451
 
                return False
452
 
            else:
453
 
                raise
454
 
 
455
 
    def feature_name(self):
456
 
        return 'strace'
457
 
 
458
 
 
459
 
strace_feature = _StraceFeature()
460
 
 
461
 
 
462
 
class _AttribFeature(Feature):
463
 
 
464
 
    def _probe(self):
465
 
        if (sys.platform not in ('cygwin', 'win32')):
466
 
            return False
467
 
        try:
468
 
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
469
 
        except OSError, e:
470
 
            return False
471
 
        return (0 == proc.wait())
472
 
 
473
 
    def feature_name(self):
474
 
        return 'attrib Windows command-line tool'
475
 
 
476
 
 
477
 
AttribFeature = _AttribFeature()
478
 
 
479
 
 
480
 
class Win32Feature(Feature):
481
 
    """Feature testing whether we're running selftest on Windows
482
 
    or Windows-like platform.
483
 
    """
484
 
 
485
 
    def _probe(self):
486
 
        return sys.platform == 'win32'
487
 
 
488
 
    def feature_name(self):
489
 
        return "win32 platform"
490
 
 
491
 
 
492
 
win32_feature = Win32Feature()
493
 
 
494
 
 
495
 
for name in ['HTTPServerFeature', 
496
 
    'HTTPSServerFeature', 'SymlinkFeature', 'HardlinkFeature',
497
 
    'OsFifoFeature', 'UnicodeFilenameFeature',
498
 
    'ByteStringNamedFilesystem', 'UTF8Filesystem',
499
 
    'BreakinFeature', 'CaseInsCasePresFilenameFeature',
500
 
    'CaseInsensitiveFilesystemFeature', 'case_sensitive_filesystem_feature',
501
 
    'posix_permissions_feature',
502
 
    ]:
503
 
    setattr(tests, name, _CompatabilityThunkFeature(
504
 
        symbol_versioning.deprecated_in((2, 5, 0)),
505
 
        'bzrlib.tests', name,
506
 
        name, 'bzrlib.tests.features'))
507
 
 
508
 
 
509
 
for (old_name, new_name) in [
510
 
    ('UnicodeFilename', 'UnicodeFilenameFeature'),
511
 
    ]:
512
 
    setattr(tests, name, _CompatabilityThunkFeature(
513
 
        symbol_versioning.deprecated_in((2, 5, 0)),
514
 
        'bzrlib.tests', old_name,
515
 
        new_name, 'bzrlib.tests.features'))
 
56
class _ChownFeature(tests.Feature):
 
57
    """os.chown is supported"""
 
58
 
 
59
    def _probe(self):
 
60
        return os.name == 'posix' and hasattr(os, 'chown')
 
61
 
 
62
chown_feature = _ChownFeature()
 
63