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
17
"""A collection of commonly used 'Features' to optionally run tests.
20
from bzrlib import tests
21
from bzrlib.symbol_versioning import deprecated_in
24
apport = tests.ModuleAvailableFeature('apport')
25
paramiko = tests.ModuleAvailableFeature('paramiko')
26
pycurl = tests.ModuleAvailableFeature('pycurl')
27
subunit = tests.ModuleAvailableFeature('subunit')
30
class _PosixPermissionsFeature(tests.Feature):
33
class Feature(object):
34
"""An operating system Feature."""
37
self._available = None
40
"""Is the feature available?
42
:return: True if the feature is available.
44
if self._available is None:
45
self._available = self._probe()
46
return self._available
49
"""Implement this method in concrete features.
51
:return: True if the feature is available.
53
raise NotImplementedError
56
if getattr(self, 'feature_name', None):
57
return self.feature_name()
58
return self.__class__.__name__
61
class _SymlinkFeature(Feature):
64
return osutils.has_symlinks()
66
def feature_name(self):
69
SymlinkFeature = _SymlinkFeature()
72
class _HardlinkFeature(Feature):
75
return osutils.has_hardlinks()
77
def feature_name(self):
80
HardlinkFeature = _HardlinkFeature()
83
class _OsFifoFeature(Feature):
86
return getattr(os, 'mkfifo', None)
88
def feature_name(self):
89
return 'filesystem fifos'
91
OsFifoFeature = _OsFifoFeature()
94
class _UnicodeFilenameFeature(Feature):
95
"""Does the filesystem support Unicode filenames?"""
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:
106
except (IOError, OSError):
107
# The filesystem allows the Unicode filename but the file doesn't
111
# The filesystem allows the Unicode filename and the file exists,
115
UnicodeFilenameFeature = _UnicodeFilenameFeature()
118
class _CompatabilityThunkFeature(Feature):
119
"""This feature is just a thunk to another feature.
121
It issues a deprecation warning if it is accessed, to let you know that you
122
should really use a different feature.
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
133
self._replacement_name = replacement_name
134
self._dep_version = dep_version
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
147
self._feature = pyutils.get_named_object(
148
self._replacement_module, self._replacement_name)
152
return self._feature._probe()
155
class ModuleAvailableFeature(Feature):
156
"""This is a feature than describes a module we want to be available.
158
Declare the name of the module in __init__(), and then after probing, the
159
module will be available as 'self.module'.
161
:ivar module: The module if it is available, else None.
164
def __init__(self, module_name):
165
super(ModuleAvailableFeature, self).__init__()
166
self.module_name = module_name
170
self._module = __import__(self.module_name, {}, {}, [''])
181
def feature_name(self):
182
return self.module_name
185
class _HTTPSServerFeature(Feature):
186
"""Some tests want an https Server, check if one is available.
188
Right now, the only way this is available is under python2.6 which provides
199
def feature_name(self):
203
HTTPSServerFeature = _HTTPSServerFeature()
206
class _ByteStringNamedFilesystem(Feature):
207
"""Is the filesystem based on bytes?"""
210
if os.name == "posix":
214
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
217
class _UTF8Filesystem(Feature):
218
"""Is the filesystem UTF-8?"""
221
if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
225
UTF8Filesystem = _UTF8Filesystem()
228
class _BreakinFeature(Feature):
229
"""Does this platform support the breakin feature?"""
232
from bzrlib import breakin
233
if breakin.determine_signal() is None:
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
245
def feature_name(self):
246
return "SIGQUIT or SIGBREAK w/ctypes on win32"
249
BreakinFeature = _BreakinFeature()
252
class _CaseInsCasePresFilenameFeature(Feature):
253
"""Is the file-system case insensitive, but case-preserving?"""
256
fileno, name = tempfile.mkstemp(prefix='MixedCase')
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()))
270
def feature_name(self):
271
return "case-insensitive case-preserving filesystem"
273
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
276
class _CaseInsensitiveFilesystemFeature(Feature):
277
"""Check if underlying filesystem is case-insensitive but *not* case
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.
284
if CaseInsCasePresFilenameFeature.available():
287
if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
288
root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
289
tests.TestCaseWithMemoryTransport.TEST_ROOT = root
291
root = tests.TestCaseWithMemoryTransport.TEST_ROOT
292
tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
294
name_a = osutils.pathjoin(tdir, 'a')
295
name_A = osutils.pathjoin(tdir, 'A')
297
result = osutils.isdir(name_A)
298
tests._rmtree_temp_dir(tdir)
301
def feature_name(self):
302
return 'case-insensitive filesystem'
304
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
307
class _CaseSensitiveFilesystemFeature(Feature):
310
if CaseInsCasePresFilenameFeature.available():
312
elif CaseInsensitiveFilesystemFeature.available():
317
def feature_name(self):
318
return 'case-sensitive filesystem'
320
# new coding style is for feature instances to be lowercase
321
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
324
class _NotRunningAsRoot(Feature):
329
except AttributeError:
330
# If there is no uid, chances are there is no root either
334
def feature_name(self):
335
return 'Not running as root'
338
not_running_as_root = _NotRunningAsRoot()
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')
351
compiled_patiencediff_feature = ModuleAvailableFeature(
352
'bzrlib._patiencediff_c')
353
meliae_feature = ModuleAvailableFeature('meliae.scanner')
354
lsprof_feature = ModuleAvailableFeature('bzrlib.lsprof')
357
class _BackslashDirSeparatorFeature(Feature):
361
os.lstat(os.getcwd() + '\\')
367
def feature_name(self):
368
return "Filesystem treats '\\' as a directory separator."
370
backslashdir_feature = _BackslashDirSeparatorFeature()
373
class _ChownFeature(Feature):
374
"""os.chown is supported"""
377
return os.name == 'posix' and hasattr(os, 'chown')
379
chown_feature = _ChownFeature()
382
class ExecutableFeature(Feature):
383
"""Feature testing whether an executable of a given name is on the PATH."""
385
def __init__(self, name):
386
super(ExecutableFeature, self).__init__()
392
# This is a property, so accessing path ensures _probe was called
397
self._path = osutils.find_executable_on_path(self.name)
398
return self._path is not None
400
def feature_name(self):
401
return '%s executable' % self.name
404
bash_feature = ExecutableFeature('bash')
405
sed_feature = ExecutableFeature('sed')
406
diff_feature = ExecutableFeature('diff')
409
class _PosixPermissionsFeature(Feature):
34
# create temporary file and check if specified perms are maintained.
413
# Create temporary file and check if specified perms are
37
415
write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
38
416
f = tempfile.mkstemp(prefix='bzr_perms_chk_')
53
431
posix_permissions_feature = _PosixPermissionsFeature()
56
class _ChownFeature(tests.Feature):
57
"""os.chown is supported"""
60
return os.name == 'posix' and hasattr(os, 'chown')
62
chown_feature = _ChownFeature()
434
class _StraceFeature(Feature):
438
proc = subprocess.Popen(['strace'],
439
stderr=subprocess.PIPE,
440
stdout=subprocess.PIPE)
444
if e.errno == errno.ENOENT:
445
# strace is not installed
450
def feature_name(self):
454
strace_feature = _StraceFeature()
457
class _AttribFeature(Feature):
460
if (sys.platform not in ('cygwin', 'win32')):
463
proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
466
return (0 == proc.wait())
468
def feature_name(self):
469
return 'attrib Windows command-line tool'
472
AttribFeature = _AttribFeature()
475
class Win32Feature(Feature):
476
"""Feature testing whether we're running selftest on Windows
477
or Windows-like platform.
481
return sys.platform == 'win32'
483
def feature_name(self):
484
return "win32 platform"
487
win32_feature = Win32Feature()
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',
498
setattr(tests, name, _CompatabilityThunkFeature(
499
symbol_versioning.deprecated_in((2, 5, 0)),
500
'bzrlib.tests', name,
501
name, 'bzrlib.tests.features'))
504
for (old_name, new_name) in [
505
('UnicodeFilename', 'UnicodeFilenameFeature'),
507
setattr(tests, name, _CompatabilityThunkFeature(
508
symbol_versioning.deprecated_in((2, 5, 0)),
509
'bzrlib.tests', old_name,
510
new_name, 'bzrlib.tests.features'))