~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/features.py

  • Committer: Martin Pool
  • Date: 2011-06-14 01:26:41 UTC
  • mto: (6034.2.1 integration)
  • mto: This revision was merged to the branch mainline in revision 6043.
  • Revision ID: mbp@canonical.com-20110614012641-dnb69zb57ae5je4w
Move all test features into bzrlib.tests.features

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
 
22
import sys
 
23
import tempfile
21
24
 
22
25
from bzrlib import (
23
26
    osutils,
 
27
    symbol_versioning,
24
28
    tests,
25
29
    )
26
30
 
27
31
 
28
 
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 _UnicodeFilename(Feature):
 
206
    """Does the filesystem support Unicode filenames?"""
 
207
 
 
208
    def _probe(self):
 
209
        try:
 
210
            os.stat(u'\u03b1')
 
211
        except UnicodeEncodeError:
 
212
            return False
 
213
        except (IOError, OSError):
 
214
            # The filesystem allows the Unicode filename but the file doesn't
 
215
            # exist.
 
216
            return True
 
217
        else:
 
218
            # The filesystem allows the Unicode filename and the file exists,
 
219
            # for some reason.
 
220
            return True
 
221
 
 
222
UnicodeFilename = _UnicodeFilename()
 
223
 
 
224
 
 
225
class _ByteStringNamedFilesystem(Feature):
 
226
    """Is the filesystem based on bytes?"""
 
227
 
 
228
    def _probe(self):
 
229
        if os.name == "posix":
 
230
            return True
 
231
        return False
 
232
 
 
233
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
 
234
 
 
235
 
 
236
class _UTF8Filesystem(Feature):
 
237
    """Is the filesystem UTF-8?"""
 
238
 
 
239
    def _probe(self):
 
240
        if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
 
241
            return True
 
242
        return False
 
243
 
 
244
UTF8Filesystem = _UTF8Filesystem()
 
245
 
 
246
 
 
247
class _BreakinFeature(Feature):
 
248
    """Does this platform support the breakin feature?"""
 
249
 
 
250
    def _probe(self):
 
251
        from bzrlib import breakin
 
252
        if breakin.determine_signal() is None:
 
253
            return False
 
254
        if sys.platform == 'win32':
 
255
            # Windows doesn't have os.kill, and we catch the SIGBREAK signal.
 
256
            # We trigger SIGBREAK via a Console api so we need ctypes to
 
257
            # access the function
 
258
            try:
 
259
                import ctypes
 
260
            except OSError:
 
261
                return False
 
262
        return True
 
263
 
 
264
    def feature_name(self):
 
265
        return "SIGQUIT or SIGBREAK w/ctypes on win32"
 
266
 
 
267
 
 
268
BreakinFeature = _BreakinFeature()
 
269
 
 
270
 
 
271
class _CaseInsCasePresFilenameFeature(Feature):
 
272
    """Is the file-system case insensitive, but case-preserving?"""
 
273
 
 
274
    def _probe(self):
 
275
        fileno, name = tempfile.mkstemp(prefix='MixedCase')
 
276
        try:
 
277
            # first check truly case-preserving for created files, then check
 
278
            # case insensitive when opening existing files.
 
279
            name = osutils.normpath(name)
 
280
            base, rel = osutils.split(name)
 
281
            found_rel = osutils.canonical_relpath(base, name)
 
282
            return (found_rel == rel
 
283
                    and os.path.isfile(name.upper())
 
284
                    and os.path.isfile(name.lower()))
 
285
        finally:
 
286
            os.close(fileno)
 
287
            os.remove(name)
 
288
 
 
289
    def feature_name(self):
 
290
        return "case-insensitive case-preserving filesystem"
 
291
 
 
292
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
 
293
 
 
294
 
 
295
class _CaseInsensitiveFilesystemFeature(Feature):
 
296
    """Check if underlying filesystem is case-insensitive but *not* case
 
297
    preserving.
 
298
    """
 
299
    # Note that on Windows, Cygwin, MacOS etc, the file-systems are far
 
300
    # more likely to be case preserving, so this case is rare.
 
301
 
 
302
    def _probe(self):
 
303
        if CaseInsCasePresFilenameFeature.available():
 
304
            return False
 
305
 
 
306
        if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
 
307
            root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
 
308
            tests.TestCaseWithMemoryTransport.TEST_ROOT = root
 
309
        else:
 
310
            root = tests.TestCaseWithMemoryTransport.TEST_ROOT
 
311
        tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
 
312
            dir=root)
 
313
        name_a = osutils.pathjoin(tdir, 'a')
 
314
        name_A = osutils.pathjoin(tdir, 'A')
 
315
        os.mkdir(name_a)
 
316
        result = osutils.isdir(name_A)
 
317
        tests._rmtree_temp_dir(tdir)
 
318
        return result
 
319
 
 
320
    def feature_name(self):
 
321
        return 'case-insensitive filesystem'
 
322
 
 
323
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
 
324
 
 
325
 
 
326
class _CaseSensitiveFilesystemFeature(Feature):
 
327
 
 
328
    def _probe(self):
 
329
        if CaseInsCasePresFilenameFeature.available():
 
330
            return False
 
331
        elif CaseInsensitiveFilesystemFeature.available():
 
332
            return False
 
333
        else:
 
334
            return True
 
335
 
 
336
    def feature_name(self):
 
337
        return 'case-sensitive filesystem'
 
338
 
 
339
# new coding style is for feature instances to be lowercase
 
340
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
 
341
 
 
342
 
 
343
class _NotRunningAsRoot(Feature):
29
344
 
30
345
    def _probe(self):
31
346
        try:
41
356
 
42
357
not_running_as_root = _NotRunningAsRoot()
43
358
 
44
 
apport = tests.ModuleAvailableFeature('apport')
45
 
lzma = tests.ModuleAvailableFeature('lzma')
46
 
meliae = tests.ModuleAvailableFeature('meliae')
47
 
paramiko = tests.ModuleAvailableFeature('paramiko')
48
 
pycurl = tests.ModuleAvailableFeature('pycurl')
49
 
pywintypes = tests.ModuleAvailableFeature('pywintypes')
50
 
sphinx = tests.ModuleAvailableFeature('sphinx')
51
 
subunit = tests.ModuleAvailableFeature('subunit')
52
 
 
53
 
 
54
 
class _BackslashDirSeparatorFeature(tests.Feature):
 
359
apport = ModuleAvailableFeature('apport')
 
360
lzma = ModuleAvailableFeature('lzma')
 
361
meliae = ModuleAvailableFeature('meliae')
 
362
paramiko = ModuleAvailableFeature('paramiko')
 
363
pycurl = ModuleAvailableFeature('pycurl')
 
364
pywintypes = ModuleAvailableFeature('pywintypes')
 
365
sphinx = ModuleAvailableFeature('sphinx')
 
366
subunit = ModuleAvailableFeature('subunit')
 
367
 
 
368
compiled_patiencediff_feature = ModuleAvailableFeature(
 
369
    'bzrlib._patiencediff_c')
 
370
meliae_feature = ModuleAvailableFeature('meliae.scanner')
 
371
lsprof_feature = ModuleAvailableFeature('bzrlib.lsprof')
 
372
 
 
373
 
 
374
class _BackslashDirSeparatorFeature(Feature):
55
375
 
56
376
    def _probe(self):
57
377
        try:
67
387
backslashdir_feature = _BackslashDirSeparatorFeature()
68
388
 
69
389
 
70
 
class _PosixPermissionsFeature(tests.Feature):
 
390
class _PosixPermissionsFeature(Feature):
71
391
 
72
392
    def _probe(self):
73
393
        def has_perms():
74
 
            # create temporary file and check if specified perms are maintained.
75
 
            import tempfile
76
 
 
 
394
            # create temporary file and check if specified perms are
 
395
            # maintained.
77
396
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
78
397
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
79
398
            fd, name = f
93
412
posix_permissions_feature = _PosixPermissionsFeature()
94
413
 
95
414
 
96
 
class _ChownFeature(tests.Feature):
 
415
class _ChownFeature(Feature):
97
416
    """os.chown is supported"""
98
417
 
99
418
    def _probe(self):
102
421
chown_feature = _ChownFeature()
103
422
 
104
423
 
105
 
class ExecutableFeature(tests.Feature):
 
424
class ExecutableFeature(Feature):
106
425
    """Feature testing whether an executable of a given name is on the PATH."""
107
426
 
108
427
    def __init__(self, name):
127
446
bash_feature = ExecutableFeature('bash')
128
447
sed_feature = ExecutableFeature('sed')
129
448
diff_feature = ExecutableFeature('diff')
 
449
 
 
450
 
 
451
class _PosixPermissionsFeature(Feature):
 
452
 
 
453
    def _probe(self):
 
454
        def has_perms():
 
455
            # Create temporary file and check if specified perms are
 
456
            # maintained.
 
457
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
 
458
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
 
459
            fd, name = f
 
460
            os.close(fd)
 
461
            os.chmod(name, write_perms)
 
462
 
 
463
            read_perms = os.stat(name).st_mode & 0777
 
464
            os.unlink(name)
 
465
            return (write_perms == read_perms)
 
466
 
 
467
        return (os.name == 'posix') and has_perms()
 
468
 
 
469
    def feature_name(self):
 
470
        return 'POSIX permissions support'
 
471
 
 
472
 
 
473
posix_permissions_feature = _PosixPermissionsFeature()
 
474
 
 
475
 
 
476
class _StraceFeature(Feature):
 
477
 
 
478
    def _probe(self):
 
479
        try:
 
480
            proc = subprocess.Popen(['strace'],
 
481
                stderr=subprocess.PIPE,
 
482
                stdout=subprocess.PIPE)
 
483
            proc.communicate()
 
484
            return True
 
485
        except OSError, e:
 
486
            if e.errno == errno.ENOENT:
 
487
                # strace is not installed
 
488
                return False
 
489
            else:
 
490
                raise
 
491
 
 
492
    def feature_name(self):
 
493
        return 'strace'
 
494
 
 
495
 
 
496
strace_feature = _StraceFeature()
 
497
 
 
498
 
 
499
class _AttribFeature(Feature):
 
500
 
 
501
    def _probe(self):
 
502
        if (sys.platform not in ('cygwin', 'win32')):
 
503
            return False
 
504
        try:
 
505
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
 
506
        except OSError, e:
 
507
            return False
 
508
        return (0 == proc.wait())
 
509
 
 
510
    def feature_name(self):
 
511
        return 'attrib Windows command-line tool'
 
512
 
 
513
 
 
514
AttribFeature = _AttribFeature()