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 __future__ import absolute_import
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):
36
class Feature(object):
37
"""An operating system Feature."""
40
self._available = None
43
"""Is the feature available?
45
:return: True if the feature is available.
47
if self._available is None:
48
self._available = self._probe()
49
return self._available
52
"""Implement this method in concrete features.
54
:return: True if the feature is available.
56
raise NotImplementedError
59
if getattr(self, 'feature_name', None):
60
return self.feature_name()
61
return self.__class__.__name__
64
class _SymlinkFeature(Feature):
67
return osutils.has_symlinks()
69
def feature_name(self):
72
SymlinkFeature = _SymlinkFeature()
75
class _HardlinkFeature(Feature):
78
return osutils.has_hardlinks()
80
def feature_name(self):
83
HardlinkFeature = _HardlinkFeature()
86
class _OsFifoFeature(Feature):
89
return getattr(os, 'mkfifo', None)
91
def feature_name(self):
92
return 'filesystem fifos'
94
OsFifoFeature = _OsFifoFeature()
97
class _UnicodeFilenameFeature(Feature):
98
"""Does the filesystem support Unicode filenames?"""
102
# Check for character combinations unlikely to be covered by any
103
# single non-unicode encoding. We use the characters
104
# - greek small letter alpha (U+03B1) and
105
# - braille pattern dots-123456 (U+283F).
106
os.stat(u'\u03b1\u283f')
107
except UnicodeEncodeError:
109
except (IOError, OSError):
110
# The filesystem allows the Unicode filename but the file doesn't
114
# The filesystem allows the Unicode filename and the file exists,
118
UnicodeFilenameFeature = _UnicodeFilenameFeature()
121
class _CompatabilityThunkFeature(Feature):
122
"""This feature is just a thunk to another feature.
124
It issues a deprecation warning if it is accessed, to let you know that you
125
should really use a different feature.
128
def __init__(self, dep_version, module, name,
129
replacement_name, replacement_module=None):
130
super(_CompatabilityThunkFeature, self).__init__()
131
self._module = module
132
if replacement_module is None:
133
replacement_module = module
134
self._replacement_module = replacement_module
136
self._replacement_name = replacement_name
137
self._dep_version = dep_version
141
if self._feature is None:
142
from breezy import pyutils
143
depr_msg = self._dep_version % ('%s.%s'
144
% (self._module, self._name))
145
use_msg = ' Use %s.%s instead.' % (self._replacement_module,
146
self._replacement_name)
147
symbol_versioning.warn(depr_msg + use_msg, DeprecationWarning,
149
# Import the new feature and use it as a replacement for the
151
self._feature = pyutils.get_named_object(
152
self._replacement_module, self._replacement_name)
156
return self._feature._probe()
159
class ModuleAvailableFeature(Feature):
160
"""This is a feature than describes a module we want to be available.
162
Declare the name of the module in __init__(), and then after probing, the
163
module will be available as 'self.module'.
165
:ivar module: The module if it is available, else None.
168
def __init__(self, module_name, ignore_warnings=None):
169
super(ModuleAvailableFeature, self).__init__()
170
self.module_name = module_name
171
if ignore_warnings is None:
173
self.ignore_warnings = ignore_warnings
177
module = sys.modules.get(self.module_name, sentinel)
178
if module is sentinel:
179
with warnings.catch_warnings():
180
for warning_category in self.ignore_warnings:
181
warnings.simplefilter('ignore', warning_category)
183
self._module = importlib.import_module(self.module_name)
188
self._module = module
197
def feature_name(self):
198
return self.module_name
201
class PluginLoadedFeature(Feature):
202
"""Check whether a plugin with specific name is loaded.
204
This is different from ModuleAvailableFeature, because
205
plugins can be available but explicitly disabled
206
(e.g. through BRZ_DISABLE_PLUGINS=blah).
208
:ivar plugin_name: The name of the plugin
211
def __init__(self, plugin_name):
212
super(PluginLoadedFeature, self).__init__()
213
self.plugin_name = plugin_name
216
from breezy.plugin import get_loaded_plugin
217
return (get_loaded_plugin(self.plugin_name) is not None)
221
from breezy.plugin import get_loaded_plugin
222
return get_loaded_plugin(self.plugin_name)
224
def feature_name(self):
225
return '%s plugin' % self.plugin_name
228
class _HTTPSServerFeature(Feature):
229
"""Some tests want an https Server, check if one is available.
231
Right now, the only way this is available is under python2.6 which provides
242
def feature_name(self):
246
HTTPSServerFeature = _HTTPSServerFeature()
249
class _ByteStringNamedFilesystem(Feature):
250
"""Is the filesystem based on bytes?"""
253
if os.name == "posix":
257
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
260
class _UTF8Filesystem(Feature):
261
"""Is the filesystem UTF-8?"""
264
if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
268
UTF8Filesystem = _UTF8Filesystem()
271
class _BreakinFeature(Feature):
272
"""Does this platform support the breakin feature?"""
275
from breezy import breakin
276
if breakin.determine_signal() is None:
278
if sys.platform == 'win32':
279
# Windows doesn't have os.kill, and we catch the SIGBREAK signal.
280
# We trigger SIGBREAK via a Console api so we need ctypes to
281
# access the function
288
def feature_name(self):
289
return "SIGQUIT or SIGBREAK w/ctypes on win32"
292
BreakinFeature = _BreakinFeature()
295
class _CaseInsCasePresFilenameFeature(Feature):
296
"""Is the file-system case insensitive, but case-preserving?"""
299
fileno, name = tempfile.mkstemp(prefix='MixedCase')
301
# first check truly case-preserving for created files, then check
302
# case insensitive when opening existing files.
303
name = osutils.normpath(name)
304
base, rel = osutils.split(name)
305
found_rel = osutils.canonical_relpath(base, name)
306
return (found_rel == rel
307
and os.path.isfile(name.upper())
308
and os.path.isfile(name.lower()))
313
def feature_name(self):
314
return "case-insensitive case-preserving filesystem"
316
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
319
class _CaseInsensitiveFilesystemFeature(Feature):
320
"""Check if underlying filesystem is case-insensitive but *not* case
323
# Note that on Windows, Cygwin, MacOS etc, the file-systems are far
324
# more likely to be case preserving, so this case is rare.
327
if CaseInsCasePresFilenameFeature.available():
330
from breezy import tests
332
if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
333
root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
334
tests.TestCaseWithMemoryTransport.TEST_ROOT = root
336
root = tests.TestCaseWithMemoryTransport.TEST_ROOT
337
tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
339
name_a = osutils.pathjoin(tdir, 'a')
340
name_A = osutils.pathjoin(tdir, 'A')
342
result = osutils.isdir(name_A)
343
tests._rmtree_temp_dir(tdir)
346
def feature_name(self):
347
return 'case-insensitive filesystem'
349
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
352
class _CaseSensitiveFilesystemFeature(Feature):
355
if CaseInsCasePresFilenameFeature.available():
357
elif CaseInsensitiveFilesystemFeature.available():
362
def feature_name(self):
363
return 'case-sensitive filesystem'
365
# new coding style is for feature instances to be lowercase
366
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
369
class _NotRunningAsRoot(Feature):
374
except AttributeError:
375
# If there is no uid, chances are there is no root either
379
def feature_name(self):
380
return 'Not running as root'
383
not_running_as_root = _NotRunningAsRoot()
385
# Apport uses deprecated imp module on python3.
386
apport = ModuleAvailableFeature(
388
ignore_warnings=[DeprecationWarning, PendingDeprecationWarning])
389
gpg = ModuleAvailableFeature('gpg')
390
lzma = ModuleAvailableFeature('lzma')
391
meliae = ModuleAvailableFeature('meliae.scanner')
392
paramiko = ModuleAvailableFeature('paramiko')
393
pywintypes = ModuleAvailableFeature('pywintypes')
394
subunit = ModuleAvailableFeature('subunit')
395
testtools = ModuleAvailableFeature('testtools')
397
compiled_patiencediff_feature = ModuleAvailableFeature(
398
'breezy._patiencediff_c')
399
lsprof_feature = ModuleAvailableFeature('breezy.lsprof')
402
class _BackslashDirSeparatorFeature(Feature):
406
os.lstat(os.getcwd() + '\\')
412
def feature_name(self):
413
return "Filesystem treats '\\' as a directory separator."
415
backslashdir_feature = _BackslashDirSeparatorFeature()
418
class _ChownFeature(Feature):
419
"""os.chown is supported"""
422
return os.name == 'posix' and hasattr(os, 'chown')
424
chown_feature = _ChownFeature()
427
class ExecutableFeature(Feature):
428
"""Feature testing whether an executable of a given name is on the PATH."""
430
def __init__(self, name):
431
super(ExecutableFeature, self).__init__()
437
# This is a property, so accessing path ensures _probe was called
442
self._path = osutils.find_executable_on_path(self.name)
443
return self._path is not None
445
def feature_name(self):
446
return '%s executable' % self.name
449
bash_feature = ExecutableFeature('bash')
450
diff_feature = ExecutableFeature('diff')
451
sed_feature = ExecutableFeature('sed')
452
msgmerge_feature = ExecutableFeature('msgmerge')
455
class _PosixPermissionsFeature(Feature):
34
# create temporary file and check if specified perms are maintained.
459
# Create temporary file and check if specified perms are
37
461
write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
38
462
f = tempfile.mkstemp(prefix='bzr_perms_chk_')
41
os.chmod(name, write_perms)
465
osutils.chmod_if_possible(name, write_perms)
43
read_perms = os.stat(name).st_mode & 0777
467
read_perms = os.stat(name).st_mode & 0o777
45
469
return (write_perms == read_perms)