/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: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-07-28 02:47:10 UTC
  • mfrom: (7519.1.1 merge-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200728024710-a2ylds219f1lsl62
Merge lp:brz/3.1.

Merged from https://code.launchpad.net/~jelmer/brz/merge-3.1/+merge/388173

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