~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/features.py

  • Committer: Patch Queue Manager
  • Date: 2011-10-14 16:54:26 UTC
  • mfrom: (6216.1.1 remove-this-file)
  • Revision ID: pqm@pqm.ubuntu.com-20111014165426-tjix4e6idryf1r2z
(jelmer) Remove an accidentally committed .THIS file. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2009, 2010, 2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""A collection of commonly used 'Features' to optionally run tests.
 
18
"""
 
19
 
 
20
import os
 
21
import subprocess
 
22
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):
 
415
 
 
416
    def _probe(self):
 
417
        def has_perms():
 
418
            # Create temporary file and check if specified perms are
 
419
            # maintained.
 
420
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
 
421
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
 
422
            fd, name = f
 
423
            os.close(fd)
 
424
            os.chmod(name, write_perms)
 
425
 
 
426
            read_perms = os.stat(name).st_mode & 0777
 
427
            os.unlink(name)
 
428
            return (write_perms == read_perms)
 
429
 
 
430
        return (os.name == 'posix') and has_perms()
 
431
 
 
432
    def feature_name(self):
 
433
        return 'POSIX permissions support'
 
434
 
 
435
 
 
436
posix_permissions_feature = _PosixPermissionsFeature()
 
437
 
 
438
 
 
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'))