/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: 2020-04-05 19:11:34 UTC
  • mto: (7490.7.16 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200405191134-0aebh8ikiwygxma5
Populate the .gitignore file.

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
 
 
73
SymlinkFeature = _SymlinkFeature()
 
74
 
 
75
 
 
76
class _HardlinkFeature(Feature):
 
77
 
 
78
    def _probe(self):
 
79
        return osutils.has_hardlinks()
 
80
 
 
81
    def feature_name(self):
 
82
        return 'hardlinks'
 
83
 
 
84
 
 
85
HardlinkFeature = _HardlinkFeature()
 
86
 
 
87
 
 
88
class _OsFifoFeature(Feature):
 
89
 
 
90
    def _probe(self):
 
91
        return getattr(os, 'mkfifo', None)
 
92
 
 
93
    def feature_name(self):
 
94
        return 'filesystem fifos'
 
95
 
 
96
 
 
97
OsFifoFeature = _OsFifoFeature()
 
98
 
 
99
 
 
100
class _UnicodeFilenameFeature(Feature):
 
101
    """Does the filesystem support Unicode filenames?"""
 
102
 
 
103
    def _probe(self):
 
104
        try:
 
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:
 
111
            return False
 
112
        except (IOError, OSError):
 
113
            # The filesystem allows the Unicode filename but the file doesn't
 
114
            # exist.
 
115
            return True
 
116
        else:
 
117
            # The filesystem allows the Unicode filename and the file exists,
 
118
            # for some reason.
 
119
            return True
 
120
 
 
121
 
 
122
UnicodeFilenameFeature = _UnicodeFilenameFeature()
 
123
 
 
124
 
 
125
class _CompatabilityThunkFeature(Feature):
 
126
    """This feature is just a thunk to another feature.
 
127
 
 
128
    It issues a deprecation warning if it is accessed, to let you know that you
 
129
    should really use a different feature.
 
130
    """
 
131
 
 
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
 
139
        self._name = name
 
140
        self._replacement_name = replacement_name
 
141
        self._dep_version = dep_version
 
142
        self._feature = None
 
143
 
 
144
    def _ensure(self):
 
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,
 
152
                                   stacklevel=5)
 
153
            # Import the new feature and use it as a replacement for the
 
154
            # deprecated one.
 
155
            self._feature = pyutils.get_named_object(
 
156
                self._replacement_module, self._replacement_name)
 
157
 
 
158
    def _probe(self):
 
159
        self._ensure()
 
160
        return self._feature._probe()
 
161
 
 
162
 
 
163
class ModuleAvailableFeature(Feature):
 
164
    """This is a feature than describes a module we want to be available.
 
165
 
 
166
    Declare the name of the module in __init__(), and then after probing, the
 
167
    module will be available as 'self.module'.
 
168
 
 
169
    :ivar module: The module if it is available, else None.
 
170
    """
 
171
 
 
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:
 
176
            ignore_warnings = ()
 
177
        self.ignore_warnings = ignore_warnings
 
178
 
 
179
    def _probe(self):
 
180
        sentinel = object()
 
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)
 
186
                try:
 
187
                    self._module = importlib.import_module(self.module_name)
 
188
                except ImportError:
 
189
                    return False
 
190
                return True
 
191
        else:
 
192
            self._module = module
 
193
            return True
 
194
 
 
195
    @property
 
196
    def module(self):
 
197
        if self.available():
 
198
            return self._module
 
199
        return None
 
200
 
 
201
    def feature_name(self):
 
202
        return self.module_name
 
203
 
 
204
 
 
205
class PluginLoadedFeature(Feature):
 
206
    """Check whether a plugin with specific name is loaded.
 
207
 
 
208
    This is different from ModuleAvailableFeature, because
 
209
    plugins can be available but explicitly disabled
 
210
    (e.g. through BRZ_DISABLE_PLUGINS=blah).
 
211
 
 
212
    :ivar plugin_name: The name of the plugin
 
213
    """
 
214
 
 
215
    def __init__(self, plugin_name):
 
216
        super(PluginLoadedFeature, self).__init__()
 
217
        self.plugin_name = plugin_name
 
218
 
 
219
    def _probe(self):
 
220
        from breezy.plugin import get_loaded_plugin
 
221
        return (get_loaded_plugin(self.plugin_name) is not None)
 
222
 
 
223
    @property
 
224
    def plugin(self):
 
225
        from breezy.plugin import get_loaded_plugin
 
226
        return get_loaded_plugin(self.plugin_name)
 
227
 
 
228
    def feature_name(self):
 
229
        return '%s plugin' % self.plugin_name
 
230
 
 
231
 
 
232
class _HTTPSServerFeature(Feature):
 
233
    """Some tests want an https Server, check if one is available.
 
234
 
 
235
    Right now, the only way this is available is under python2.6 which provides
 
236
    an ssl module.
 
237
    """
 
238
 
 
239
    def _probe(self):
 
240
        try:
 
241
            import ssl  # noqa: F401
 
242
            return True
 
243
        except ImportError:
 
244
            return False
 
245
 
 
246
    def feature_name(self):
 
247
        return 'HTTPSServer'
 
248
 
 
249
 
 
250
HTTPSServerFeature = _HTTPSServerFeature()
 
251
 
 
252
 
 
253
class _ByteStringNamedFilesystem(Feature):
 
254
    """Is the filesystem based on bytes?"""
 
255
 
 
256
    def _probe(self):
 
257
        if os.name == "posix":
 
258
            return True
 
259
        return False
 
260
 
 
261
 
 
262
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
 
263
 
 
264
 
 
265
class _UTF8Filesystem(Feature):
 
266
    """Is the filesystem UTF-8?"""
 
267
 
 
268
    def _probe(self):
 
269
        if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
 
270
            return True
 
271
        return False
 
272
 
 
273
 
 
274
UTF8Filesystem = _UTF8Filesystem()
 
275
 
 
276
 
 
277
class _BreakinFeature(Feature):
 
278
    """Does this platform support the breakin feature?"""
 
279
 
 
280
    def _probe(self):
 
281
        from breezy import breakin
 
282
        if breakin.determine_signal() is None:
 
283
            return False
 
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
 
288
            try:
 
289
                import ctypes  # noqa: F401
 
290
            except OSError:
 
291
                return False
 
292
        return True
 
293
 
 
294
    def feature_name(self):
 
295
        return "SIGQUIT or SIGBREAK w/ctypes on win32"
 
296
 
 
297
 
 
298
BreakinFeature = _BreakinFeature()
 
299
 
 
300
 
 
301
class _CaseInsCasePresFilenameFeature(Feature):
 
302
    """Is the file-system case insensitive, but case-preserving?"""
 
303
 
 
304
    def _probe(self):
 
305
        fileno, name = tempfile.mkstemp(prefix='MixedCase')
 
306
        try:
 
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()))
 
315
        finally:
 
316
            os.close(fileno)
 
317
            os.remove(name)
 
318
 
 
319
    def feature_name(self):
 
320
        return "case-insensitive case-preserving filesystem"
 
321
 
 
322
 
 
323
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
 
324
 
 
325
 
 
326
class _CaseInsensitiveFilesystemFeature(Feature):
 
327
    """Check if underlying filesystem is case-insensitive but *not* case
 
328
    preserving.
 
329
    """
 
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.
 
332
 
 
333
    def _probe(self):
 
334
        if CaseInsCasePresFilenameFeature.available():
 
335
            return False
 
336
 
 
337
        from breezy import tests
 
338
 
 
339
        if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
 
340
            root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
 
341
            tests.TestCaseWithMemoryTransport.TEST_ROOT = root
 
342
        else:
 
343
            root = tests.TestCaseWithMemoryTransport.TEST_ROOT
 
344
        tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
 
345
                               dir=root)
 
346
        name_a = osutils.pathjoin(tdir, 'a')
 
347
        name_A = osutils.pathjoin(tdir, 'A')
 
348
        os.mkdir(name_a)
 
349
        result = osutils.isdir(name_A)
 
350
        tests._rmtree_temp_dir(tdir)
 
351
        return result
 
352
 
 
353
    def feature_name(self):
 
354
        return 'case-insensitive filesystem'
 
355
 
 
356
 
 
357
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
 
358
 
 
359
 
 
360
class _CaseSensitiveFilesystemFeature(Feature):
 
361
 
 
362
    def _probe(self):
 
363
        if CaseInsCasePresFilenameFeature.available():
 
364
            return False
 
365
        elif CaseInsensitiveFilesystemFeature.available():
 
366
            return False
 
367
        else:
 
368
            return True
 
369
 
 
370
    def feature_name(self):
 
371
        return 'case-sensitive filesystem'
 
372
 
 
373
 
 
374
# new coding style is for feature instances to be lowercase
 
375
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
 
376
 
 
377
 
 
378
class _NotRunningAsRoot(Feature):
 
379
 
 
380
    def _probe(self):
 
381
        try:
 
382
            uid = os.getuid()
 
383
        except AttributeError:
 
384
            # If there is no uid, chances are there is no root either
 
385
            return True
 
386
        return uid != 0
 
387
 
 
388
    def feature_name(self):
 
389
        return 'Not running as root'
 
390
 
 
391
 
 
392
not_running_as_root = _NotRunningAsRoot()
 
393
 
 
394
# Apport uses deprecated imp module on python3.
 
395
apport = ModuleAvailableFeature(
 
396
    'apport.report',
 
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')
 
406
 
 
407
lsprof_feature = ModuleAvailableFeature('breezy.lsprof')
 
408
pkg_resources_feature = ModuleAvailableFeature('pkg_resources')
 
409
 
 
410
 
 
411
class _BackslashDirSeparatorFeature(Feature):
 
412
 
 
413
    def _probe(self):
 
414
        try:
 
415
            os.lstat(os.getcwd() + '\\')
 
416
        except OSError:
 
417
            return False
 
418
        else:
 
419
            return True
 
420
 
 
421
    def feature_name(self):
 
422
        return "Filesystem treats '\\' as a directory separator."
 
423
 
 
424
 
 
425
backslashdir_feature = _BackslashDirSeparatorFeature()
 
426
 
 
427
 
 
428
class _ChownFeature(Feature):
 
429
    """os.chown is supported"""
 
430
 
 
431
    def _probe(self):
 
432
        return os.name == 'posix' and hasattr(os, 'chown')
 
433
 
 
434
 
 
435
chown_feature = _ChownFeature()
 
436
 
 
437
 
 
438
class ExecutableFeature(Feature):
 
439
    """Feature testing whether an executable of a given name is on the PATH."""
 
440
 
 
441
    def __init__(self, name):
 
442
        super(ExecutableFeature, self).__init__()
 
443
        self.name = name
 
444
        self._path = None
 
445
 
 
446
    @property
 
447
    def path(self):
 
448
        # This is a property, so accessing path ensures _probe was called
 
449
        self.available()
 
450
        return self._path
 
451
 
 
452
    def _probe(self):
 
453
        self._path = osutils.find_executable_on_path(self.name)
 
454
        return self._path is not None
 
455
 
 
456
    def feature_name(self):
 
457
        return '%s executable' % self.name
 
458
 
 
459
 
 
460
bash_feature = ExecutableFeature('bash')
 
461
diff_feature = ExecutableFeature('diff')
 
462
sed_feature = ExecutableFeature('sed')
 
463
msgmerge_feature = ExecutableFeature('msgmerge')
 
464
 
 
465
 
 
466
class _PosixPermissionsFeature(Feature):
31
467
 
32
468
    def _probe(self):
33
469
        def has_perms():
34
 
            # create temporary file and check if specified perms are maintained.
35
 
            import tempfile
36
 
 
 
470
            # Create temporary file and check if specified perms are
 
471
            # maintained.
37
472
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
38
473
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
39
474
            fd, name = f
40
475
            os.close(fd)
41
 
            os.chmod(name, write_perms)
 
476
            osutils.chmod_if_possible(name, write_perms)
42
477
 
43
 
            read_perms = os.stat(name).st_mode & 0777
 
478
            read_perms = os.stat(name).st_mode & 0o777
44
479
            os.unlink(name)
45
480
            return (write_perms == read_perms)
46
481
 
53
488
posix_permissions_feature = _PosixPermissionsFeature()
54
489
 
55
490
 
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
 
 
 
491
class _StraceFeature(Feature):
 
492
 
 
493
    def _probe(self):
 
494
        try:
 
495
            proc = subprocess.Popen(['strace'],
 
496
                                    stderr=subprocess.PIPE,
 
497
                                    stdout=subprocess.PIPE)
 
498
            proc.communicate()
 
499
            return True
 
500
        except OSError as e:
 
501
            import errno
 
502
            if e.errno == errno.ENOENT:
 
503
                # strace is not installed
 
504
                return False
 
505
            else:
 
506
                raise
 
507
 
 
508
    def feature_name(self):
 
509
        return 'strace'
 
510
 
 
511
 
 
512
strace_feature = _StraceFeature()
 
513
 
 
514
 
 
515
class _AttribFeature(Feature):
 
516
 
 
517
    def _probe(self):
 
518
        if (sys.platform not in ('cygwin', 'win32')):
 
519
            return False
 
520
        try:
 
521
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
 
522
        except OSError:
 
523
            return False
 
524
        return (0 == proc.wait())
 
525
 
 
526
    def feature_name(self):
 
527
        return 'attrib Windows command-line tool'
 
528
 
 
529
 
 
530
AttribFeature = _AttribFeature()
 
531
 
 
532
 
 
533
class Win32Feature(Feature):
 
534
    """Feature testing whether we're running selftest on Windows
 
535
    or Windows-like platform.
 
536
    """
 
537
 
 
538
    def _probe(self):
 
539
        return sys.platform == 'win32'
 
540
 
 
541
    def feature_name(self):
 
542
        return "win32 platform"
 
543
 
 
544
 
 
545
win32_feature = Win32Feature()
 
546
 
 
547
 
 
548
class _BackslashFilenameFeature(Feature):
 
549
    """Does the filesystem support backslashes in filenames?"""
 
550
 
 
551
    def _probe(self):
 
552
 
 
553
        try:
 
554
            fileno, name = tempfile.mkstemp(prefix='bzr\\prefix')
 
555
        except (IOError, OSError):
 
556
            return False
 
557
        else:
 
558
            try:
 
559
                os.stat(name)
 
560
            except (IOError, OSError):
 
561
                # mkstemp succeeded but the file wasn't actually created
 
562
                return False
 
563
            os.close(fileno)
 
564
            os.remove(name)
 
565
            return True
 
566
 
 
567
 
 
568
BackslashFilenameFeature = _BackslashFilenameFeature()
 
569
 
 
570
 
 
571
class PathFeature(Feature):
 
572
    """Feature testing whether a particular path exists."""
 
573
 
 
574
    def __init__(self, path):
 
575
        super(PathFeature, self).__init__()
 
576
        self.path = path
 
577
 
 
578
    def _probe(self):
 
579
        return os.path.exists(self.path)
 
580
 
 
581
    def feature_name(self):
 
582
        return "%s exists" % self.path