~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/features.py

  • Committer: Vincent Ladeuil
  • Date: 2011-08-12 09:49:24 UTC
  • mfrom: (6015.9.10 2.4)
  • mto: This revision was merged to the branch mainline in revision 6066.
  • Revision ID: v.ladeuil+lp@free.fr-20110812094924-knc5s0g7vs31a2f1
Merge 2.4 into trunk

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