/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/features.py

  • Committer: Jelmer Vernooij
  • Date: 2018-11-16 16:20:27 UTC
  • mto: This revision was merged to the branch mainline in revision 7174.
  • Revision ID: jelmer@jelmer.uk-20181116162027-6v6i0kpyfi54blso
Use addCleanup.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2009, 2010, 2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
16
16
 
 
17
"""A collection of commonly used 'Features' to optionally run tests.
 
18
"""
 
19
 
 
20
from __future__ import absolute_import
 
21
 
 
22
import importlib
17
23
import os
 
24
import subprocess
18
25
import stat
19
 
 
20
 
from bzrlib import tests
21
 
from bzrlib.symbol_versioning import deprecated_in
22
 
 
23
 
 
24
 
apport = tests.ModuleAvailableFeature('apport')
25
 
paramiko = tests.ModuleAvailableFeature('paramiko')
26
 
pycurl = tests.ModuleAvailableFeature('pycurl')
27
 
subunit = tests.ModuleAvailableFeature('subunit')
28
 
 
29
 
 
30
 
class _PosixPermissionsFeature(tests.Feature):
 
26
import sys
 
27
import tempfile
 
28
import warnings
 
29
 
 
30
from .. import (
 
31
    osutils,
 
32
    symbol_versioning,
 
33
    )
 
34
 
 
35
 
 
36
class Feature(object):
 
37
    """An operating system Feature."""
 
38
 
 
39
    def __init__(self):
 
40
        self._available = None
 
41
 
 
42
    def available(self):
 
43
        """Is the feature available?
 
44
 
 
45
        :return: True if the feature is available.
 
46
        """
 
47
        if self._available is None:
 
48
            self._available = self._probe()
 
49
        return self._available
 
50
 
 
51
    def _probe(self):
 
52
        """Implement this method in concrete features.
 
53
 
 
54
        :return: True if the feature is available.
 
55
        """
 
56
        raise NotImplementedError
 
57
 
 
58
    def __str__(self):
 
59
        if getattr(self, 'feature_name', None):
 
60
            return self.feature_name()
 
61
        return self.__class__.__name__
 
62
 
 
63
 
 
64
class _SymlinkFeature(Feature):
 
65
 
 
66
    def _probe(self):
 
67
        return osutils.has_symlinks()
 
68
 
 
69
    def feature_name(self):
 
70
        return 'symlinks'
 
71
 
 
72
SymlinkFeature = _SymlinkFeature()
 
73
 
 
74
 
 
75
class _HardlinkFeature(Feature):
 
76
 
 
77
    def _probe(self):
 
78
        return osutils.has_hardlinks()
 
79
 
 
80
    def feature_name(self):
 
81
        return 'hardlinks'
 
82
 
 
83
HardlinkFeature = _HardlinkFeature()
 
84
 
 
85
 
 
86
class _OsFifoFeature(Feature):
 
87
 
 
88
    def _probe(self):
 
89
        return getattr(os, 'mkfifo', None)
 
90
 
 
91
    def feature_name(self):
 
92
        return 'filesystem fifos'
 
93
 
 
94
OsFifoFeature = _OsFifoFeature()
 
95
 
 
96
 
 
97
class _UnicodeFilenameFeature(Feature):
 
98
    """Does the filesystem support Unicode filenames?"""
 
99
 
 
100
    def _probe(self):
 
101
        try:
 
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:
 
108
            return False
 
109
        except (IOError, OSError):
 
110
            # The filesystem allows the Unicode filename but the file doesn't
 
111
            # exist.
 
112
            return True
 
113
        else:
 
114
            # The filesystem allows the Unicode filename and the file exists,
 
115
            # for some reason.
 
116
            return True
 
117
 
 
118
UnicodeFilenameFeature = _UnicodeFilenameFeature()
 
119
 
 
120
 
 
121
class _CompatabilityThunkFeature(Feature):
 
122
    """This feature is just a thunk to another feature.
 
123
 
 
124
    It issues a deprecation warning if it is accessed, to let you know that you
 
125
    should really use a different feature.
 
126
    """
 
127
 
 
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
 
135
        self._name = name
 
136
        self._replacement_name = replacement_name
 
137
        self._dep_version = dep_version
 
138
        self._feature = None
 
139
 
 
140
    def _ensure(self):
 
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,
 
148
                                   stacklevel=5)
 
149
            # Import the new feature and use it as a replacement for the
 
150
            # deprecated one.
 
151
            self._feature = pyutils.get_named_object(
 
152
                self._replacement_module, self._replacement_name)
 
153
 
 
154
    def _probe(self):
 
155
        self._ensure()
 
156
        return self._feature._probe()
 
157
 
 
158
 
 
159
class ModuleAvailableFeature(Feature):
 
160
    """This is a feature than describes a module we want to be available.
 
161
 
 
162
    Declare the name of the module in __init__(), and then after probing, the
 
163
    module will be available as 'self.module'.
 
164
 
 
165
    :ivar module: The module if it is available, else None.
 
166
    """
 
167
 
 
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:
 
172
            ignore_warnings = ()
 
173
        self.ignore_warnings = ignore_warnings
 
174
 
 
175
    def _probe(self):
 
176
        sentinel = object()
 
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)
 
182
                try:
 
183
                    self._module = importlib.import_module(self.module_name)
 
184
                except ImportError:
 
185
                    return False
 
186
                return True
 
187
        else:
 
188
            self._module = module
 
189
            return True
 
190
 
 
191
    @property
 
192
    def module(self):
 
193
        if self.available():
 
194
            return self._module
 
195
        return None
 
196
 
 
197
    def feature_name(self):
 
198
        return self.module_name
 
199
 
 
200
 
 
201
class PluginLoadedFeature(Feature):
 
202
    """Check whether a plugin with specific name is loaded.
 
203
 
 
204
    This is different from ModuleAvailableFeature, because
 
205
    plugins can be available but explicitly disabled
 
206
    (e.g. through BRZ_DISABLE_PLUGINS=blah).
 
207
 
 
208
    :ivar plugin_name: The name of the plugin
 
209
    """
 
210
 
 
211
    def __init__(self, plugin_name):
 
212
        super(PluginLoadedFeature, self).__init__()
 
213
        self.plugin_name = plugin_name
 
214
 
 
215
    def _probe(self):
 
216
        from breezy.plugin import get_loaded_plugin
 
217
        return (get_loaded_plugin(self.plugin_name) is not None)
 
218
 
 
219
    @property
 
220
    def plugin(self):
 
221
        from breezy.plugin import get_loaded_plugin
 
222
        return get_loaded_plugin(self.plugin_name)
 
223
 
 
224
    def feature_name(self):
 
225
        return '%s plugin' % self.plugin_name
 
226
 
 
227
 
 
228
class _HTTPSServerFeature(Feature):
 
229
    """Some tests want an https Server, check if one is available.
 
230
 
 
231
    Right now, the only way this is available is under python2.6 which provides
 
232
    an ssl module.
 
233
    """
 
234
 
 
235
    def _probe(self):
 
236
        try:
 
237
            import ssl
 
238
            return True
 
239
        except ImportError:
 
240
            return False
 
241
 
 
242
    def feature_name(self):
 
243
        return 'HTTPSServer'
 
244
 
 
245
 
 
246
HTTPSServerFeature = _HTTPSServerFeature()
 
247
 
 
248
 
 
249
class _ByteStringNamedFilesystem(Feature):
 
250
    """Is the filesystem based on bytes?"""
 
251
 
 
252
    def _probe(self):
 
253
        if os.name == "posix":
 
254
            return True
 
255
        return False
 
256
 
 
257
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
 
258
 
 
259
 
 
260
class _UTF8Filesystem(Feature):
 
261
    """Is the filesystem UTF-8?"""
 
262
 
 
263
    def _probe(self):
 
264
        if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
 
265
            return True
 
266
        return False
 
267
 
 
268
UTF8Filesystem = _UTF8Filesystem()
 
269
 
 
270
 
 
271
class _BreakinFeature(Feature):
 
272
    """Does this platform support the breakin feature?"""
 
273
 
 
274
    def _probe(self):
 
275
        from breezy import breakin
 
276
        if breakin.determine_signal() is None:
 
277
            return False
 
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
 
282
            try:
 
283
                import ctypes
 
284
            except OSError:
 
285
                return False
 
286
        return True
 
287
 
 
288
    def feature_name(self):
 
289
        return "SIGQUIT or SIGBREAK w/ctypes on win32"
 
290
 
 
291
 
 
292
BreakinFeature = _BreakinFeature()
 
293
 
 
294
 
 
295
class _CaseInsCasePresFilenameFeature(Feature):
 
296
    """Is the file-system case insensitive, but case-preserving?"""
 
297
 
 
298
    def _probe(self):
 
299
        fileno, name = tempfile.mkstemp(prefix='MixedCase')
 
300
        try:
 
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()))
 
309
        finally:
 
310
            os.close(fileno)
 
311
            os.remove(name)
 
312
 
 
313
    def feature_name(self):
 
314
        return "case-insensitive case-preserving filesystem"
 
315
 
 
316
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
 
317
 
 
318
 
 
319
class _CaseInsensitiveFilesystemFeature(Feature):
 
320
    """Check if underlying filesystem is case-insensitive but *not* case
 
321
    preserving.
 
322
    """
 
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.
 
325
 
 
326
    def _probe(self):
 
327
        if CaseInsCasePresFilenameFeature.available():
 
328
            return False
 
329
 
 
330
        from breezy import tests
 
331
 
 
332
        if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
 
333
            root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
 
334
            tests.TestCaseWithMemoryTransport.TEST_ROOT = root
 
335
        else:
 
336
            root = tests.TestCaseWithMemoryTransport.TEST_ROOT
 
337
        tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
 
338
            dir=root)
 
339
        name_a = osutils.pathjoin(tdir, 'a')
 
340
        name_A = osutils.pathjoin(tdir, 'A')
 
341
        os.mkdir(name_a)
 
342
        result = osutils.isdir(name_A)
 
343
        tests._rmtree_temp_dir(tdir)
 
344
        return result
 
345
 
 
346
    def feature_name(self):
 
347
        return 'case-insensitive filesystem'
 
348
 
 
349
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
 
350
 
 
351
 
 
352
class _CaseSensitiveFilesystemFeature(Feature):
 
353
 
 
354
    def _probe(self):
 
355
        if CaseInsCasePresFilenameFeature.available():
 
356
            return False
 
357
        elif CaseInsensitiveFilesystemFeature.available():
 
358
            return False
 
359
        else:
 
360
            return True
 
361
 
 
362
    def feature_name(self):
 
363
        return 'case-sensitive filesystem'
 
364
 
 
365
# new coding style is for feature instances to be lowercase
 
366
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
 
367
 
 
368
 
 
369
class _NotRunningAsRoot(Feature):
 
370
 
 
371
    def _probe(self):
 
372
        try:
 
373
            uid = os.getuid()
 
374
        except AttributeError:
 
375
            # If there is no uid, chances are there is no root either
 
376
            return True
 
377
        return uid != 0
 
378
 
 
379
    def feature_name(self):
 
380
        return 'Not running as root'
 
381
 
 
382
 
 
383
not_running_as_root = _NotRunningAsRoot()
 
384
 
 
385
# Apport uses deprecated imp module on python3.
 
386
apport = ModuleAvailableFeature(
 
387
    'apport.report',
 
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')
 
396
flake8 = ModuleAvailableFeature('flake8')
 
397
 
 
398
compiled_patiencediff_feature = ModuleAvailableFeature(
 
399
    'breezy._patiencediff_c')
 
400
lsprof_feature = ModuleAvailableFeature('breezy.lsprof')
 
401
 
 
402
 
 
403
class _BackslashDirSeparatorFeature(Feature):
 
404
 
 
405
    def _probe(self):
 
406
        try:
 
407
            os.lstat(os.getcwd() + '\\')
 
408
        except OSError:
 
409
            return False
 
410
        else:
 
411
            return True
 
412
 
 
413
    def feature_name(self):
 
414
        return "Filesystem treats '\\' as a directory separator."
 
415
 
 
416
backslashdir_feature = _BackslashDirSeparatorFeature()
 
417
 
 
418
 
 
419
class _ChownFeature(Feature):
 
420
    """os.chown is supported"""
 
421
 
 
422
    def _probe(self):
 
423
        return os.name == 'posix' and hasattr(os, 'chown')
 
424
 
 
425
chown_feature = _ChownFeature()
 
426
 
 
427
 
 
428
class ExecutableFeature(Feature):
 
429
    """Feature testing whether an executable of a given name is on the PATH."""
 
430
 
 
431
    def __init__(self, name):
 
432
        super(ExecutableFeature, self).__init__()
 
433
        self.name = name
 
434
        self._path = None
 
435
 
 
436
    @property
 
437
    def path(self):
 
438
        # This is a property, so accessing path ensures _probe was called
 
439
        self.available()
 
440
        return self._path
 
441
 
 
442
    def _probe(self):
 
443
        self._path = osutils.find_executable_on_path(self.name)
 
444
        return self._path is not None
 
445
 
 
446
    def feature_name(self):
 
447
        return '%s executable' % self.name
 
448
 
 
449
 
 
450
bash_feature = ExecutableFeature('bash')
 
451
diff_feature = ExecutableFeature('diff')
 
452
sed_feature = ExecutableFeature('sed')
 
453
msgmerge_feature = ExecutableFeature('msgmerge')
 
454
 
 
455
 
 
456
class _PosixPermissionsFeature(Feature):
31
457
 
32
458
    def _probe(self):
33
459
        def has_perms():
34
 
            # create temporary file and check if specified perms are maintained.
35
 
            import tempfile
36
 
 
 
460
            # Create temporary file and check if specified perms are
 
461
            # maintained.
37
462
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
38
463
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
39
464
            fd, name = f
40
465
            os.close(fd)
41
 
            os.chmod(name, write_perms)
 
466
            osutils.chmod_if_possible(name, write_perms)
42
467
 
43
 
            read_perms = os.stat(name).st_mode & 0777
 
468
            read_perms = os.stat(name).st_mode & 0o777
44
469
            os.unlink(name)
45
470
            return (write_perms == read_perms)
46
471
 
53
478
posix_permissions_feature = _PosixPermissionsFeature()
54
479
 
55
480
 
56
 
class _ChownFeature(tests.Feature):
57
 
    """os.chown is supported"""
58
 
 
59
 
    def _probe(self):
60
 
        return os.name == 'posix' and hasattr(os, 'chown')
61
 
 
62
 
chown_feature = _ChownFeature()
63
 
 
 
481
class _StraceFeature(Feature):
 
482
 
 
483
    def _probe(self):
 
484
        try:
 
485
            proc = subprocess.Popen(['strace'],
 
486
                stderr=subprocess.PIPE,
 
487
                stdout=subprocess.PIPE)
 
488
            proc.communicate()
 
489
            return True
 
490
        except OSError as e:
 
491
            if e.errno == errno.ENOENT:
 
492
                # strace is not installed
 
493
                return False
 
494
            else:
 
495
                raise
 
496
 
 
497
    def feature_name(self):
 
498
        return 'strace'
 
499
 
 
500
 
 
501
strace_feature = _StraceFeature()
 
502
 
 
503
 
 
504
class _AttribFeature(Feature):
 
505
 
 
506
    def _probe(self):
 
507
        if (sys.platform not in ('cygwin', 'win32')):
 
508
            return False
 
509
        try:
 
510
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
 
511
        except OSError as e:
 
512
            return False
 
513
        return (0 == proc.wait())
 
514
 
 
515
    def feature_name(self):
 
516
        return 'attrib Windows command-line tool'
 
517
 
 
518
 
 
519
AttribFeature = _AttribFeature()
 
520
 
 
521
 
 
522
class Win32Feature(Feature):
 
523
    """Feature testing whether we're running selftest on Windows
 
524
    or Windows-like platform.
 
525
    """
 
526
 
 
527
    def _probe(self):
 
528
        return sys.platform == 'win32'
 
529
 
 
530
    def feature_name(self):
 
531
        return "win32 platform"
 
532
 
 
533
 
 
534
win32_feature = Win32Feature()
 
535
 
 
536
 
 
537
class _BackslashFilenameFeature(Feature):
 
538
    """Does the filesystem support backslashes in filenames?"""
 
539
 
 
540
    def _probe(self):
 
541
 
 
542
        try:
 
543
            fileno, name = tempfile.mkstemp(prefix='bzr\\prefix')
 
544
        except (IOError, OSError):
 
545
            return False
 
546
        else:
 
547
            try:
 
548
                os.stat(name)
 
549
            except (IOError, OSError):
 
550
                # mkstemp succeeded but the file wasn't actually created
 
551
                return False
 
552
            os.close(fileno)
 
553
            os.remove(name)
 
554
            return True
 
555
 
 
556
 
 
557
BackslashFilenameFeature = _BackslashFilenameFeature()