1
# Copyright (C) 2009, 2010, 2011 Canonical Ltd
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.
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.
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
17
"""A collection of commonly used 'Features' to optionally run tests.
34
class Feature(object):
35
"""An operating system Feature."""
38
self._available = None
41
"""Is the feature available?
43
:return: True if the feature is available.
45
if self._available is None:
46
self._available = self._probe()
47
return self._available
50
"""Implement this method in concrete features.
52
:return: True if the feature is available.
54
raise NotImplementedError
57
if getattr(self, 'feature_name', None):
58
return self.feature_name()
59
return self.__class__.__name__
62
class _SymlinkFeature(Feature):
65
return osutils.has_symlinks()
67
def feature_name(self):
71
SymlinkFeature = _SymlinkFeature()
74
class _HardlinkFeature(Feature):
77
return osutils.has_hardlinks()
79
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'
95
OsFifoFeature = _OsFifoFeature()
98
class _UnicodeFilenameFeature(Feature):
99
"""Does the filesystem support Unicode filenames?"""
103
# Check for character combinations unlikely to be covered by any
104
# single non-unicode encoding. We use the characters
105
# - greek small letter alpha (U+03B1) and
106
# - braille pattern dots-123456 (U+283F).
107
os.stat(u'\u03b1\u283f')
108
except UnicodeEncodeError:
110
except (IOError, OSError):
111
# The filesystem allows the Unicode filename but the file doesn't
115
# The filesystem allows the Unicode filename and the file exists,
120
UnicodeFilenameFeature = _UnicodeFilenameFeature()
123
class _CompatabilityThunkFeature(Feature):
124
"""This feature is just a thunk to another feature.
126
It issues a deprecation warning if it is accessed, to let you know that you
127
should really use a different feature.
130
def __init__(self, dep_version, module, name,
131
replacement_name, replacement_module=None):
132
super(_CompatabilityThunkFeature, self).__init__()
133
self._module = module
134
if replacement_module is None:
135
replacement_module = module
136
self._replacement_module = replacement_module
138
self._replacement_name = replacement_name
139
self._dep_version = dep_version
143
if self._feature is None:
144
from breezy import pyutils
145
depr_msg = self._dep_version % ('%s.%s'
146
% (self._module, self._name))
147
use_msg = ' Use %s.%s instead.' % (self._replacement_module,
148
self._replacement_name)
149
symbol_versioning.warn(depr_msg + use_msg, DeprecationWarning,
151
# Import the new feature and use it as a replacement for the
153
self._feature = pyutils.get_named_object(
154
self._replacement_module, self._replacement_name)
158
return self._feature._probe()
161
class ModuleAvailableFeature(Feature):
162
"""This is a feature than describes a module we want to be available.
164
Declare the name of the module in __init__(), and then after probing, the
165
module will be available as 'self.module'.
167
:ivar module: The module if it is available, else None.
170
def __init__(self, module_name, ignore_warnings=None):
171
super(ModuleAvailableFeature, self).__init__()
172
self.module_name = module_name
173
if ignore_warnings is None:
175
self.ignore_warnings = ignore_warnings
179
module = sys.modules.get(self.module_name, sentinel)
180
if module is sentinel:
181
with warnings.catch_warnings():
182
for warning_category in self.ignore_warnings:
183
warnings.simplefilter('ignore', warning_category)
185
self._module = importlib.import_module(self.module_name)
190
self._module = module
199
def feature_name(self):
200
return self.module_name
203
class PluginLoadedFeature(Feature):
204
"""Check whether a plugin with specific name is loaded.
206
This is different from ModuleAvailableFeature, because
207
plugins can be available but explicitly disabled
208
(e.g. through BRZ_DISABLE_PLUGINS=blah).
210
:ivar plugin_name: The name of the plugin
213
def __init__(self, plugin_name):
214
super(PluginLoadedFeature, self).__init__()
215
self.plugin_name = plugin_name
218
from breezy.plugin import get_loaded_plugin
219
return (get_loaded_plugin(self.plugin_name) is not None)
223
from breezy.plugin import get_loaded_plugin
224
return get_loaded_plugin(self.plugin_name)
226
def feature_name(self):
227
return '%s plugin' % self.plugin_name
230
class _HTTPSServerFeature(Feature):
231
"""Some tests want an https Server, check if one is available.
233
Right now, the only way this is available is under python2.6 which provides
239
import ssl # noqa: F401
244
def feature_name(self):
248
HTTPSServerFeature = _HTTPSServerFeature()
251
class _ByteStringNamedFilesystem(Feature):
252
"""Is the filesystem based on bytes?"""
255
if os.name == "posix":
260
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
263
class _UTF8Filesystem(Feature):
264
"""Is the filesystem UTF-8?"""
267
if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
272
UTF8Filesystem = _UTF8Filesystem()
275
class _BreakinFeature(Feature):
276
"""Does this platform support the breakin feature?"""
279
from breezy import breakin
280
if breakin.determine_signal() is None:
282
if sys.platform == 'win32':
283
# Windows doesn't have os.kill, and we catch the SIGBREAK signal.
284
# We trigger SIGBREAK via a Console api so we need ctypes to
285
# access the function
287
import ctypes # noqa: F401
292
def feature_name(self):
293
return "SIGQUIT or SIGBREAK w/ctypes on win32"
296
BreakinFeature = _BreakinFeature()
299
class _CaseInsCasePresFilenameFeature(Feature):
300
"""Is the file-system case insensitive, but case-preserving?"""
303
fileno, name = tempfile.mkstemp(prefix='MixedCase')
305
# first check truly case-preserving for created files, then check
306
# case insensitive when opening existing files.
307
name = osutils.normpath(name)
308
base, rel = osutils.split(name)
309
found_rel = osutils.canonical_relpath(base, name)
310
return (found_rel == rel and
311
os.path.isfile(name.upper()) and
312
os.path.isfile(name.lower()))
317
def feature_name(self):
318
return "case-insensitive case-preserving filesystem"
321
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
324
class _CaseInsensitiveFilesystemFeature(Feature):
325
"""Check if underlying filesystem is case-insensitive but *not* case
328
# Note that on Windows, Cygwin, MacOS etc, the file-systems are far
329
# more likely to be case preserving, so this case is rare.
332
if CaseInsCasePresFilenameFeature.available():
335
from breezy import tests
337
if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
338
root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
339
tests.TestCaseWithMemoryTransport.TEST_ROOT = root
341
root = tests.TestCaseWithMemoryTransport.TEST_ROOT
342
tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
344
name_a = osutils.pathjoin(tdir, 'a')
345
name_A = osutils.pathjoin(tdir, 'A')
347
result = osutils.isdir(name_A)
348
tests._rmtree_temp_dir(tdir)
351
def feature_name(self):
352
return 'case-insensitive filesystem'
355
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
358
class _CaseSensitiveFilesystemFeature(Feature):
361
if CaseInsCasePresFilenameFeature.available():
363
elif CaseInsensitiveFilesystemFeature.available():
368
def feature_name(self):
369
return 'case-sensitive filesystem'
372
# new coding style is for feature instances to be lowercase
373
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
376
class _NotRunningAsRoot(Feature):
381
except AttributeError:
382
# If there is no uid, chances are there is no root either
386
def feature_name(self):
387
return 'Not running as root'
390
not_running_as_root = _NotRunningAsRoot()
392
# Apport uses deprecated imp module on python3.
393
apport = ModuleAvailableFeature(
395
ignore_warnings=[DeprecationWarning, PendingDeprecationWarning])
396
gpg = ModuleAvailableFeature('gpg')
397
lzma = ModuleAvailableFeature('lzma')
398
meliae = ModuleAvailableFeature('meliae.scanner')
399
paramiko = ModuleAvailableFeature('paramiko')
400
pywintypes = ModuleAvailableFeature('pywintypes')
401
subunit = ModuleAvailableFeature('subunit')
402
testtools = ModuleAvailableFeature('testtools')
403
flake8 = ModuleAvailableFeature('flake8.api.legacy')
405
lsprof_feature = ModuleAvailableFeature('breezy.lsprof')
406
pkg_resources_feature = ModuleAvailableFeature('pkg_resources')
408
pyinotify = ModuleAvailableFeature('pyinotify')
411
class _BackslashDirSeparatorFeature(Feature):
415
os.lstat(os.getcwd() + '\\')
421
def feature_name(self):
422
return "Filesystem treats '\\' as a directory separator."
425
backslashdir_feature = _BackslashDirSeparatorFeature()
428
class _ChownFeature(Feature):
429
"""os.chown is supported"""
432
return os.name == 'posix' and hasattr(os, 'chown')
435
chown_feature = _ChownFeature()
438
class ExecutableFeature(Feature):
439
"""Feature testing whether an executable of a given name is on the PATH."""
441
def __init__(self, name):
442
super(ExecutableFeature, self).__init__()
448
# This is a property, so accessing path ensures _probe was called
453
self._path = osutils.find_executable_on_path(self.name)
454
return self._path is not None
456
def feature_name(self):
457
return '%s executable' % self.name
460
bash_feature = ExecutableFeature('bash')
461
diff_feature = ExecutableFeature('diff')
462
sed_feature = ExecutableFeature('sed')
463
msgmerge_feature = ExecutableFeature('msgmerge')
466
class _PosixPermissionsFeature(Feature):
470
# Create temporary file and check if specified perms are
472
write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
473
f = tempfile.mkstemp(prefix='bzr_perms_chk_')
476
osutils.chmod_if_possible(name, write_perms)
478
read_perms = os.stat(name).st_mode & 0o777
480
return (write_perms == read_perms)
482
return (os.name == 'posix') and has_perms()
484
def feature_name(self):
485
return 'POSIX permissions support'
488
posix_permissions_feature = _PosixPermissionsFeature()
491
class _StraceFeature(Feature):
495
proc = subprocess.Popen(['strace'],
496
stderr=subprocess.PIPE,
497
stdout=subprocess.PIPE)
502
if e.errno == errno.ENOENT:
503
# strace is not installed
508
def feature_name(self):
512
strace_feature = _StraceFeature()
515
class _AttribFeature(Feature):
518
if (sys.platform not in ('cygwin', 'win32')):
521
proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
524
return (0 == proc.wait())
526
def feature_name(self):
527
return 'attrib Windows command-line tool'
530
AttribFeature = _AttribFeature()
533
class Win32Feature(Feature):
534
"""Feature testing whether we're running selftest on Windows
535
or Windows-like platform.
539
return sys.platform == 'win32'
541
def feature_name(self):
542
return "win32 platform"
545
win32_feature = Win32Feature()
548
class _BackslashFilenameFeature(Feature):
549
"""Does the filesystem support backslashes in filenames?"""
554
fileno, name = tempfile.mkstemp(prefix='bzr\\prefix')
555
except (IOError, OSError):
560
except (IOError, OSError):
561
# mkstemp succeeded but the file wasn't actually created
568
BackslashFilenameFeature = _BackslashFilenameFeature()
571
class PathFeature(Feature):
572
"""Feature testing whether a particular path exists."""
574
def __init__(self, path):
575
super(PathFeature, self).__init__()
579
return os.path.exists(self.path)
581
def feature_name(self):
582
return "%s exists" % self.path