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.
20
from __future__ import absolute_import
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):
73
SymlinkFeature = _SymlinkFeature()
76
class _HardlinkFeature(Feature):
79
return osutils.has_hardlinks()
81
def feature_name(self):
85
HardlinkFeature = _HardlinkFeature()
88
class _OsFifoFeature(Feature):
91
return getattr(os, 'mkfifo', None)
93
def feature_name(self):
94
return 'filesystem fifos'
97
OsFifoFeature = _OsFifoFeature()
100
class _UnicodeFilenameFeature(Feature):
101
"""Does the filesystem support Unicode filenames?"""
105
# Check for character combinations unlikely to be covered by any
106
# single non-unicode encoding. We use the characters
107
# - greek small letter alpha (U+03B1) and
108
# - braille pattern dots-123456 (U+283F).
109
os.stat(u'\u03b1\u283f')
110
except UnicodeEncodeError:
112
except (IOError, OSError):
113
# The filesystem allows the Unicode filename but the file doesn't
117
# The filesystem allows the Unicode filename and the file exists,
122
UnicodeFilenameFeature = _UnicodeFilenameFeature()
125
class _CompatabilityThunkFeature(Feature):
126
"""This feature is just a thunk to another feature.
128
It issues a deprecation warning if it is accessed, to let you know that you
129
should really use a different feature.
132
def __init__(self, dep_version, module, name,
133
replacement_name, replacement_module=None):
134
super(_CompatabilityThunkFeature, self).__init__()
135
self._module = module
136
if replacement_module is None:
137
replacement_module = module
138
self._replacement_module = replacement_module
140
self._replacement_name = replacement_name
141
self._dep_version = dep_version
145
if self._feature is None:
146
from breezy import pyutils
147
depr_msg = self._dep_version % ('%s.%s'
148
% (self._module, self._name))
149
use_msg = ' Use %s.%s instead.' % (self._replacement_module,
150
self._replacement_name)
151
symbol_versioning.warn(depr_msg + use_msg, DeprecationWarning,
153
# Import the new feature and use it as a replacement for the
155
self._feature = pyutils.get_named_object(
156
self._replacement_module, self._replacement_name)
160
return self._feature._probe()
163
class ModuleAvailableFeature(Feature):
164
"""This is a feature than describes a module we want to be available.
166
Declare the name of the module in __init__(), and then after probing, the
167
module will be available as 'self.module'.
169
:ivar module: The module if it is available, else None.
172
def __init__(self, module_name, ignore_warnings=None):
173
super(ModuleAvailableFeature, self).__init__()
174
self.module_name = module_name
175
if ignore_warnings is None:
177
self.ignore_warnings = ignore_warnings
181
module = sys.modules.get(self.module_name, sentinel)
182
if module is sentinel:
183
with warnings.catch_warnings():
184
for warning_category in self.ignore_warnings:
185
warnings.simplefilter('ignore', warning_category)
187
self._module = importlib.import_module(self.module_name)
192
self._module = module
201
def feature_name(self):
202
return self.module_name
205
class PluginLoadedFeature(Feature):
206
"""Check whether a plugin with specific name is loaded.
208
This is different from ModuleAvailableFeature, because
209
plugins can be available but explicitly disabled
210
(e.g. through BRZ_DISABLE_PLUGINS=blah).
212
:ivar plugin_name: The name of the plugin
215
def __init__(self, plugin_name):
216
super(PluginLoadedFeature, self).__init__()
217
self.plugin_name = plugin_name
220
from breezy.plugin import get_loaded_plugin
221
return (get_loaded_plugin(self.plugin_name) is not None)
225
from breezy.plugin import get_loaded_plugin
226
return get_loaded_plugin(self.plugin_name)
228
def feature_name(self):
229
return '%s plugin' % self.plugin_name
232
class _HTTPSServerFeature(Feature):
233
"""Some tests want an https Server, check if one is available.
235
Right now, the only way this is available is under python2.6 which provides
241
import ssl # noqa: F401
246
def feature_name(self):
250
HTTPSServerFeature = _HTTPSServerFeature()
253
class _ByteStringNamedFilesystem(Feature):
254
"""Is the filesystem based on bytes?"""
257
if os.name == "posix":
262
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
265
class _UTF8Filesystem(Feature):
266
"""Is the filesystem UTF-8?"""
269
if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
274
UTF8Filesystem = _UTF8Filesystem()
277
class _BreakinFeature(Feature):
278
"""Does this platform support the breakin feature?"""
281
from breezy import breakin
282
if breakin.determine_signal() is None:
284
if sys.platform == 'win32':
285
# Windows doesn't have os.kill, and we catch the SIGBREAK signal.
286
# We trigger SIGBREAK via a Console api so we need ctypes to
287
# access the function
289
import ctypes # noqa: F401
294
def feature_name(self):
295
return "SIGQUIT or SIGBREAK w/ctypes on win32"
298
BreakinFeature = _BreakinFeature()
301
class _CaseInsCasePresFilenameFeature(Feature):
302
"""Is the file-system case insensitive, but case-preserving?"""
305
fileno, name = tempfile.mkstemp(prefix='MixedCase')
307
# first check truly case-preserving for created files, then check
308
# case insensitive when opening existing files.
309
name = osutils.normpath(name)
310
base, rel = osutils.split(name)
311
found_rel = osutils.canonical_relpath(base, name)
312
return (found_rel == rel and
313
os.path.isfile(name.upper()) and
314
os.path.isfile(name.lower()))
319
def feature_name(self):
320
return "case-insensitive case-preserving filesystem"
323
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
326
class _CaseInsensitiveFilesystemFeature(Feature):
327
"""Check if underlying filesystem is case-insensitive but *not* case
330
# Note that on Windows, Cygwin, MacOS etc, the file-systems are far
331
# more likely to be case preserving, so this case is rare.
334
if CaseInsCasePresFilenameFeature.available():
337
from breezy import tests
339
if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
340
root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
341
tests.TestCaseWithMemoryTransport.TEST_ROOT = root
343
root = tests.TestCaseWithMemoryTransport.TEST_ROOT
344
tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
346
name_a = osutils.pathjoin(tdir, 'a')
347
name_A = osutils.pathjoin(tdir, 'A')
349
result = osutils.isdir(name_A)
350
tests._rmtree_temp_dir(tdir)
353
def feature_name(self):
354
return 'case-insensitive filesystem'
357
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
360
class _CaseSensitiveFilesystemFeature(Feature):
363
if CaseInsCasePresFilenameFeature.available():
365
elif CaseInsensitiveFilesystemFeature.available():
370
def feature_name(self):
371
return 'case-sensitive filesystem'
374
# new coding style is for feature instances to be lowercase
375
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
378
class _NotRunningAsRoot(Feature):
383
except AttributeError:
384
# If there is no uid, chances are there is no root either
388
def feature_name(self):
389
return 'Not running as root'
392
not_running_as_root = _NotRunningAsRoot()
394
# Apport uses deprecated imp module on python3.
395
apport = ModuleAvailableFeature(
397
ignore_warnings=[DeprecationWarning, PendingDeprecationWarning])
398
gpg = ModuleAvailableFeature('gpg')
399
lzma = ModuleAvailableFeature('lzma')
400
meliae = ModuleAvailableFeature('meliae.scanner')
401
paramiko = ModuleAvailableFeature('paramiko')
402
pywintypes = ModuleAvailableFeature('pywintypes')
403
subunit = ModuleAvailableFeature('subunit')
404
testtools = ModuleAvailableFeature('testtools')
405
flake8 = ModuleAvailableFeature('flake8.api.legacy')
407
lsprof_feature = ModuleAvailableFeature('breezy.lsprof')
408
pkg_resources_feature = ModuleAvailableFeature('pkg_resources')
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