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.
32
class Feature(object):
33
"""An operating system Feature."""
36
self._available = None
39
"""Is the feature available?
41
:return: True if the feature is available.
43
if self._available is None:
44
self._available = self._probe()
45
return self._available
48
"""Implement this method in concrete features.
50
:return: True if the feature is available.
52
raise NotImplementedError
55
if getattr(self, 'feature_name', None):
56
return self.feature_name()
57
return self.__class__.__name__
60
class _SymlinkFeature(Feature):
63
return osutils.has_symlinks()
65
def feature_name(self):
68
SymlinkFeature = _SymlinkFeature()
71
class _HardlinkFeature(Feature):
74
return osutils.has_hardlinks()
76
def feature_name(self):
79
HardlinkFeature = _HardlinkFeature()
82
class _OsFifoFeature(Feature):
85
return getattr(os, 'mkfifo', None)
87
def feature_name(self):
88
return 'filesystem fifos'
90
OsFifoFeature = _OsFifoFeature()
93
class _UnicodeFilenameFeature(Feature):
94
"""Does the filesystem support Unicode filenames?"""
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:
105
except (IOError, OSError):
106
# The filesystem allows the Unicode filename but the file doesn't
110
# The filesystem allows the Unicode filename and the file exists,
114
UnicodeFilenameFeature = _UnicodeFilenameFeature()
117
class _CompatabilityThunkFeature(Feature):
118
"""This feature is just a thunk to another feature.
120
It issues a deprecation warning if it is accessed, to let you know that you
121
should really use a different feature.
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
132
self._replacement_name = replacement_name
133
self._dep_version = dep_version
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
146
self._feature = pyutils.get_named_object(
147
self._replacement_module, self._replacement_name)
151
return self._feature._probe()
154
class ModuleAvailableFeature(Feature):
155
"""This is a feature than describes a module we want to be available.
157
Declare the name of the module in __init__(), and then after probing, the
158
module will be available as 'self.module'.
160
:ivar module: The module if it is available, else None.
163
def __init__(self, module_name):
164
super(ModuleAvailableFeature, self).__init__()
165
self.module_name = module_name
169
self._module = __import__(self.module_name, {}, {}, [''])
180
def feature_name(self):
181
return self.module_name
184
class _HTTPSServerFeature(Feature):
185
"""Some tests want an https Server, check if one is available.
187
Right now, the only way this is available is under python2.6 which provides
198
def feature_name(self):
202
HTTPSServerFeature = _HTTPSServerFeature()
205
class _ByteStringNamedFilesystem(Feature):
206
"""Is the filesystem based on bytes?"""
209
if os.name == "posix":
213
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
216
class _UTF8Filesystem(Feature):
217
"""Is the filesystem UTF-8?"""
220
if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
224
UTF8Filesystem = _UTF8Filesystem()
227
class _BreakinFeature(Feature):
228
"""Does this platform support the breakin feature?"""
231
from bzrlib import breakin
232
if breakin.determine_signal() is None:
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
244
def feature_name(self):
245
return "SIGQUIT or SIGBREAK w/ctypes on win32"
248
BreakinFeature = _BreakinFeature()
251
class _CaseInsCasePresFilenameFeature(Feature):
252
"""Is the file-system case insensitive, but case-preserving?"""
255
fileno, name = tempfile.mkstemp(prefix='MixedCase')
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()))
269
def feature_name(self):
270
return "case-insensitive case-preserving filesystem"
272
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
275
class _CaseInsensitiveFilesystemFeature(Feature):
276
"""Check if underlying filesystem is case-insensitive but *not* case
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.
283
if CaseInsCasePresFilenameFeature.available():
286
if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
287
root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
288
tests.TestCaseWithMemoryTransport.TEST_ROOT = root
290
root = tests.TestCaseWithMemoryTransport.TEST_ROOT
291
tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
293
name_a = osutils.pathjoin(tdir, 'a')
294
name_A = osutils.pathjoin(tdir, 'A')
296
result = osutils.isdir(name_A)
297
tests._rmtree_temp_dir(tdir)
300
def feature_name(self):
301
return 'case-insensitive filesystem'
303
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
306
class _CaseSensitiveFilesystemFeature(Feature):
309
if CaseInsCasePresFilenameFeature.available():
311
elif CaseInsensitiveFilesystemFeature.available():
316
def feature_name(self):
317
return 'case-sensitive filesystem'
319
# new coding style is for feature instances to be lowercase
320
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
323
class _NotRunningAsRoot(Feature):
328
except AttributeError:
329
# If there is no uid, chances are there is no root either
333
def feature_name(self):
334
return 'Not running as root'
337
not_running_as_root = _NotRunningAsRoot()
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')
350
compiled_patiencediff_feature = ModuleAvailableFeature(
351
'bzrlib._patiencediff_c')
352
meliae_feature = ModuleAvailableFeature('meliae.scanner')
353
lsprof_feature = ModuleAvailableFeature('bzrlib.lsprof')
356
class _BackslashDirSeparatorFeature(Feature):
360
os.lstat(os.getcwd() + '\\')
366
def feature_name(self):
367
return "Filesystem treats '\\' as a directory separator."
369
backslashdir_feature = _BackslashDirSeparatorFeature()
372
class _ChownFeature(Feature):
373
"""os.chown is supported"""
376
return os.name == 'posix' and hasattr(os, 'chown')
378
chown_feature = _ChownFeature()
381
class ExecutableFeature(Feature):
382
"""Feature testing whether an executable of a given name is on the PATH."""
384
def __init__(self, name):
385
super(ExecutableFeature, self).__init__()
391
# This is a property, so accessing path ensures _probe was called
396
self._path = osutils.find_executable_on_path(self.name)
397
return self._path is not None
399
def feature_name(self):
400
return '%s executable' % self.name
403
bash_feature = ExecutableFeature('bash')
404
sed_feature = ExecutableFeature('sed')
405
diff_feature = ExecutableFeature('diff')
408
class _PosixPermissionsFeature(Feature):
412
# Create temporary file and check if specified perms are
414
write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
415
f = tempfile.mkstemp(prefix='bzr_perms_chk_')
418
os.chmod(name, write_perms)
420
read_perms = os.stat(name).st_mode & 0777
422
return (write_perms == read_perms)
424
return (os.name == 'posix') and has_perms()
426
def feature_name(self):
427
return 'POSIX permissions support'
430
posix_permissions_feature = _PosixPermissionsFeature()
433
class _StraceFeature(Feature):
437
proc = subprocess.Popen(['strace'],
438
stderr=subprocess.PIPE,
439
stdout=subprocess.PIPE)
443
if e.errno == errno.ENOENT:
444
# strace is not installed
449
def feature_name(self):
453
strace_feature = _StraceFeature()
456
class _AttribFeature(Feature):
459
if (sys.platform not in ('cygwin', 'win32')):
462
proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
465
return (0 == proc.wait())
467
def feature_name(self):
468
return 'attrib Windows command-line tool'
471
AttribFeature = _AttribFeature()
474
class Win32Feature(Feature):
475
"""Feature testing whether we're running selftest on Windows
476
or Windows-like platform.
480
return sys.platform == 'win32'
482
def feature_name(self):
483
return "win32 platform"
486
win32_feature = Win32Feature()
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',
497
setattr(tests, name, _CompatabilityThunkFeature(
498
symbol_versioning.deprecated_in((2, 5, 0)),
499
'bzrlib.tests', name,
500
name, 'bzrlib.tests.features'))
503
for (old_name, new_name) in [
504
('UnicodeFilename', 'UnicodeFilenameFeature'),
506
setattr(tests, name, _CompatabilityThunkFeature(
507
symbol_versioning.deprecated_in((2, 5, 0)),
508
'bzrlib.tests', old_name,
509
new_name, 'bzrlib.tests.features'))
18
from bzrlib import tests
19
from bzrlib.symbol_versioning import deprecated_in
22
apport = tests.ModuleAvailableFeature('apport')
23
ApportFeature = tests._CompatabilityThunkFeature('bzrlib.tests.features',
24
'ApportFeature', 'bzrlib.tests.features.apport', deprecated_in((2,1,0)))
25
paramiko = tests.ModuleAvailableFeature('paramiko')
26
pycurl = tests.ModuleAvailableFeature('pycurl')
27
subunit = tests.ModuleAvailableFeature('subunit')