~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: 2011-08-15 13:49:38 UTC
  • mfrom: (6060.3.1 fix-last-revno-args)
  • Revision ID: pqm@pqm.ubuntu.com-20110815134938-4fuo63g4v2hj8jdt
(jelmer) Cope with the localhost having the name 'localhost' when running
 the test suite. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
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' which bzrlib uses to skip tests."""
 
17
"""A collection of commonly used 'Features' to optionally run tests.
 
18
"""
18
19
 
19
20
import os
 
21
import subprocess
20
22
import stat
21
23
import sys
 
24
import tempfile
22
25
 
23
26
from bzrlib import (
24
27
    osutils,
 
28
    symbol_versioning,
25
29
    tests,
26
30
    )
27
31
 
28
32
 
29
 
class _NotRunningAsRoot(tests.Feature):
 
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
        try:
 
170
            self._module = __import__(self.module_name, {}, {}, [''])
 
171
            return True
 
172
        except ImportError:
 
173
            return False
 
174
 
 
175
    @property
 
176
    def module(self):
 
177
        if self.available():
 
178
            return self._module
 
179
        return None
 
180
 
 
181
    def feature_name(self):
 
182
        return self.module_name
 
183
 
 
184
 
 
185
class _HTTPSServerFeature(Feature):
 
186
    """Some tests want an https Server, check if one is available.
 
187
 
 
188
    Right now, the only way this is available is under python2.6 which provides
 
189
    an ssl module.
 
190
    """
 
191
 
 
192
    def _probe(self):
 
193
        try:
 
194
            import ssl
 
195
            return True
 
196
        except ImportError:
 
197
            return False
 
198
 
 
199
    def feature_name(self):
 
200
        return 'HTTPSServer'
 
201
 
 
202
 
 
203
HTTPSServerFeature = _HTTPSServerFeature()
 
204
 
 
205
 
 
206
class _ByteStringNamedFilesystem(Feature):
 
207
    """Is the filesystem based on bytes?"""
 
208
 
 
209
    def _probe(self):
 
210
        if os.name == "posix":
 
211
            return True
 
212
        return False
 
213
 
 
214
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
 
215
 
 
216
 
 
217
class _UTF8Filesystem(Feature):
 
218
    """Is the filesystem UTF-8?"""
 
219
 
 
220
    def _probe(self):
 
221
        if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
 
222
            return True
 
223
        return False
 
224
 
 
225
UTF8Filesystem = _UTF8Filesystem()
 
226
 
 
227
 
 
228
class _BreakinFeature(Feature):
 
229
    """Does this platform support the breakin feature?"""
 
230
 
 
231
    def _probe(self):
 
232
        from bzrlib import breakin
 
233
        if breakin.determine_signal() is None:
 
234
            return False
 
235
        if sys.platform == 'win32':
 
236
            # Windows doesn't have os.kill, and we catch the SIGBREAK signal.
 
237
            # We trigger SIGBREAK via a Console api so we need ctypes to
 
238
            # access the function
 
239
            try:
 
240
                import ctypes
 
241
            except OSError:
 
242
                return False
 
243
        return True
 
244
 
 
245
    def feature_name(self):
 
246
        return "SIGQUIT or SIGBREAK w/ctypes on win32"
 
247
 
 
248
 
 
249
BreakinFeature = _BreakinFeature()
 
250
 
 
251
 
 
252
class _CaseInsCasePresFilenameFeature(Feature):
 
253
    """Is the file-system case insensitive, but case-preserving?"""
 
254
 
 
255
    def _probe(self):
 
256
        fileno, name = tempfile.mkstemp(prefix='MixedCase')
 
257
        try:
 
258
            # first check truly case-preserving for created files, then check
 
259
            # case insensitive when opening existing files.
 
260
            name = osutils.normpath(name)
 
261
            base, rel = osutils.split(name)
 
262
            found_rel = osutils.canonical_relpath(base, name)
 
263
            return (found_rel == rel
 
264
                    and os.path.isfile(name.upper())
 
265
                    and os.path.isfile(name.lower()))
 
266
        finally:
 
267
            os.close(fileno)
 
268
            os.remove(name)
 
269
 
 
270
    def feature_name(self):
 
271
        return "case-insensitive case-preserving filesystem"
 
272
 
 
273
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
 
274
 
 
275
 
 
276
class _CaseInsensitiveFilesystemFeature(Feature):
 
277
    """Check if underlying filesystem is case-insensitive but *not* case
 
278
    preserving.
 
279
    """
 
280
    # Note that on Windows, Cygwin, MacOS etc, the file-systems are far
 
281
    # more likely to be case preserving, so this case is rare.
 
282
 
 
283
    def _probe(self):
 
284
        if CaseInsCasePresFilenameFeature.available():
 
285
            return False
 
286
 
 
287
        if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
 
288
            root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
 
289
            tests.TestCaseWithMemoryTransport.TEST_ROOT = root
 
290
        else:
 
291
            root = tests.TestCaseWithMemoryTransport.TEST_ROOT
 
292
        tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
 
293
            dir=root)
 
294
        name_a = osutils.pathjoin(tdir, 'a')
 
295
        name_A = osutils.pathjoin(tdir, 'A')
 
296
        os.mkdir(name_a)
 
297
        result = osutils.isdir(name_A)
 
298
        tests._rmtree_temp_dir(tdir)
 
299
        return result
 
300
 
 
301
    def feature_name(self):
 
302
        return 'case-insensitive filesystem'
 
303
 
 
304
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
 
305
 
 
306
 
 
307
class _CaseSensitiveFilesystemFeature(Feature):
 
308
 
 
309
    def _probe(self):
 
310
        if CaseInsCasePresFilenameFeature.available():
 
311
            return False
 
312
        elif CaseInsensitiveFilesystemFeature.available():
 
313
            return False
 
314
        else:
 
315
            return True
 
316
 
 
317
    def feature_name(self):
 
318
        return 'case-sensitive filesystem'
 
319
 
 
320
# new coding style is for feature instances to be lowercase
 
321
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
 
322
 
 
323
 
 
324
class _NotRunningAsRoot(Feature):
30
325
 
31
326
    def _probe(self):
32
327
        try:
42
337
 
43
338
not_running_as_root = _NotRunningAsRoot()
44
339
 
45
 
apport = tests.ModuleAvailableFeature('apport')
46
 
gpgme = tests.ModuleAvailableFeature('gpgme')
47
 
lzma = tests.ModuleAvailableFeature('lzma')
48
 
meliae = tests.ModuleAvailableFeature('meliae')
49
 
paramiko = tests.ModuleAvailableFeature('paramiko')
50
 
pycurl = tests.ModuleAvailableFeature('pycurl')
51
 
pywintypes = tests.ModuleAvailableFeature('pywintypes')
52
 
sphinx = tests.ModuleAvailableFeature('sphinx')
53
 
subunit = tests.ModuleAvailableFeature('subunit')
54
 
testtools = tests.ModuleAvailableFeature('testtools')
55
 
 
56
 
 
57
 
class _BackslashDirSeparatorFeature(tests.Feature):
 
340
apport = ModuleAvailableFeature('apport')
 
341
gpgme = ModuleAvailableFeature('gpgme')
 
342
lzma = ModuleAvailableFeature('lzma')
 
343
meliae = ModuleAvailableFeature('meliae')
 
344
paramiko = ModuleAvailableFeature('paramiko')
 
345
pycurl = ModuleAvailableFeature('pycurl')
 
346
pywintypes = ModuleAvailableFeature('pywintypes')
 
347
sphinx = ModuleAvailableFeature('sphinx')
 
348
subunit = ModuleAvailableFeature('subunit')
 
349
testtools = ModuleAvailableFeature('testtools')
 
350
 
 
351
compiled_patiencediff_feature = ModuleAvailableFeature(
 
352
    'bzrlib._patiencediff_c')
 
353
meliae_feature = ModuleAvailableFeature('meliae.scanner')
 
354
lsprof_feature = ModuleAvailableFeature('bzrlib.lsprof')
 
355
 
 
356
 
 
357
class _BackslashDirSeparatorFeature(Feature):
58
358
 
59
359
    def _probe(self):
60
360
        try:
70
370
backslashdir_feature = _BackslashDirSeparatorFeature()
71
371
 
72
372
 
73
 
class _PosixPermissionsFeature(tests.Feature):
74
 
 
75
 
    def _probe(self):
76
 
        def has_perms():
77
 
            # create temporary file and check if specified perms are maintained.
78
 
            import tempfile
79
 
 
80
 
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
81
 
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
82
 
            fd, name = f
83
 
            os.close(fd)
84
 
            os.chmod(name, write_perms)
85
 
 
86
 
            read_perms = os.stat(name).st_mode & 0777
87
 
            os.unlink(name)
88
 
            return (write_perms == read_perms)
89
 
 
90
 
        return (os.name == 'posix') and has_perms()
91
 
 
92
 
    def feature_name(self):
93
 
        return 'POSIX permissions support'
94
 
 
95
 
 
96
 
posix_permissions_feature = _PosixPermissionsFeature()
97
 
 
98
 
 
99
 
class _ChownFeature(tests.Feature):
 
373
class _ChownFeature(Feature):
100
374
    """os.chown is supported"""
101
375
 
102
376
    def _probe(self):
105
379
chown_feature = _ChownFeature()
106
380
 
107
381
 
108
 
class ExecutableFeature(tests.Feature):
 
382
class ExecutableFeature(Feature):
109
383
    """Feature testing whether an executable of a given name is on the PATH."""
110
384
 
111
385
    def __init__(self, name):
132
406
diff_feature = ExecutableFeature('diff')
133
407
 
134
408
 
135
 
class Win32Feature(tests.Feature):
 
409
class _PosixPermissionsFeature(Feature):
 
410
 
 
411
    def _probe(self):
 
412
        def has_perms():
 
413
            # Create temporary file and check if specified perms are
 
414
            # maintained.
 
415
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
 
416
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
 
417
            fd, name = f
 
418
            os.close(fd)
 
419
            os.chmod(name, write_perms)
 
420
 
 
421
            read_perms = os.stat(name).st_mode & 0777
 
422
            os.unlink(name)
 
423
            return (write_perms == read_perms)
 
424
 
 
425
        return (os.name == 'posix') and has_perms()
 
426
 
 
427
    def feature_name(self):
 
428
        return 'POSIX permissions support'
 
429
 
 
430
 
 
431
posix_permissions_feature = _PosixPermissionsFeature()
 
432
 
 
433
 
 
434
class _StraceFeature(Feature):
 
435
 
 
436
    def _probe(self):
 
437
        try:
 
438
            proc = subprocess.Popen(['strace'],
 
439
                stderr=subprocess.PIPE,
 
440
                stdout=subprocess.PIPE)
 
441
            proc.communicate()
 
442
            return True
 
443
        except OSError, e:
 
444
            if e.errno == errno.ENOENT:
 
445
                # strace is not installed
 
446
                return False
 
447
            else:
 
448
                raise
 
449
 
 
450
    def feature_name(self):
 
451
        return 'strace'
 
452
 
 
453
 
 
454
strace_feature = _StraceFeature()
 
455
 
 
456
 
 
457
class _AttribFeature(Feature):
 
458
 
 
459
    def _probe(self):
 
460
        if (sys.platform not in ('cygwin', 'win32')):
 
461
            return False
 
462
        try:
 
463
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
 
464
        except OSError, e:
 
465
            return False
 
466
        return (0 == proc.wait())
 
467
 
 
468
    def feature_name(self):
 
469
        return 'attrib Windows command-line tool'
 
470
 
 
471
 
 
472
AttribFeature = _AttribFeature()
 
473
 
 
474
 
 
475
class Win32Feature(Feature):
136
476
    """Feature testing whether we're running selftest on Windows
137
477
    or Windows-like platform.
138
478
    """
143
483
    def feature_name(self):
144
484
        return "win32 platform"
145
485
 
 
486
 
146
487
win32_feature = Win32Feature()
 
488
 
 
489
 
 
490
for name in ['HTTPServerFeature', 
 
491
    'HTTPSServerFeature', 'SymlinkFeature', 'HardlinkFeature',
 
492
    'OsFifoFeature', 'UnicodeFilenameFeature',
 
493
    'ByteStringNamedFilesystem', 'UTF8Filesystem',
 
494
    'BreakinFeature', 'CaseInsCasePresFilenameFeature',
 
495
    'CaseInsensitiveFilesystemFeature', 'case_sensitive_filesystem_feature',
 
496
    'posix_permissions_feature',
 
497
    ]:
 
498
    setattr(tests, name, _CompatabilityThunkFeature(
 
499
        symbol_versioning.deprecated_in((2, 5, 0)),
 
500
        'bzrlib.tests', name,
 
501
        name, 'bzrlib.tests.features'))
 
502
 
 
503
 
 
504
for (old_name, new_name) in [
 
505
    ('UnicodeFilename', 'UnicodeFilenameFeature'),
 
506
    ]:
 
507
    setattr(tests, name, _CompatabilityThunkFeature(
 
508
        symbol_versioning.deprecated_in((2, 5, 0)),
 
509
        'bzrlib.tests', old_name,
 
510
        new_name, 'bzrlib.tests.features'))