/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: 2019-02-04 01:01:24 UTC
  • mto: This revision was merged to the branch mainline in revision 7268.
  • Revision ID: jelmer@jelmer.uk-20190204010124-ni0i4qc6f5tnbvux
Fix source tests.

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
compiled_patiencediff_feature = ModuleAvailableFeature(
 
408
    'breezy._patiencediff_c')
 
409
lsprof_feature = ModuleAvailableFeature('breezy.lsprof')
 
410
pkg_resources_feature = ModuleAvailableFeature('pkg_resources')
 
411
 
 
412
 
 
413
class _BackslashDirSeparatorFeature(Feature):
 
414
 
 
415
    def _probe(self):
 
416
        try:
 
417
            os.lstat(os.getcwd() + '\\')
 
418
        except OSError:
 
419
            return False
 
420
        else:
 
421
            return True
 
422
 
 
423
    def feature_name(self):
 
424
        return "Filesystem treats '\\' as a directory separator."
 
425
 
 
426
 
 
427
backslashdir_feature = _BackslashDirSeparatorFeature()
 
428
 
 
429
 
 
430
class _ChownFeature(Feature):
 
431
    """os.chown is supported"""
 
432
 
 
433
    def _probe(self):
 
434
        return os.name == 'posix' and hasattr(os, 'chown')
 
435
 
 
436
 
 
437
chown_feature = _ChownFeature()
 
438
 
 
439
 
 
440
class ExecutableFeature(Feature):
 
441
    """Feature testing whether an executable of a given name is on the PATH."""
 
442
 
 
443
    def __init__(self, name):
 
444
        super(ExecutableFeature, self).__init__()
 
445
        self.name = name
 
446
        self._path = None
 
447
 
 
448
    @property
 
449
    def path(self):
 
450
        # This is a property, so accessing path ensures _probe was called
 
451
        self.available()
 
452
        return self._path
 
453
 
 
454
    def _probe(self):
 
455
        self._path = osutils.find_executable_on_path(self.name)
 
456
        return self._path is not None
 
457
 
 
458
    def feature_name(self):
 
459
        return '%s executable' % self.name
 
460
 
 
461
 
 
462
bash_feature = ExecutableFeature('bash')
 
463
diff_feature = ExecutableFeature('diff')
 
464
sed_feature = ExecutableFeature('sed')
 
465
msgmerge_feature = ExecutableFeature('msgmerge')
 
466
 
 
467
 
 
468
class _PosixPermissionsFeature(Feature):
31
469
 
32
470
    def _probe(self):
33
471
        def has_perms():
34
 
            # create temporary file and check if specified perms are maintained.
35
 
            import tempfile
36
 
 
 
472
            # Create temporary file and check if specified perms are
 
473
            # maintained.
37
474
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
38
475
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
39
476
            fd, name = f
40
477
            os.close(fd)
41
 
            os.chmod(name, write_perms)
 
478
            osutils.chmod_if_possible(name, write_perms)
42
479
 
43
 
            read_perms = os.stat(name).st_mode & 0777
 
480
            read_perms = os.stat(name).st_mode & 0o777
44
481
            os.unlink(name)
45
482
            return (write_perms == read_perms)
46
483
 
53
490
posix_permissions_feature = _PosixPermissionsFeature()
54
491
 
55
492
 
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
 
 
 
493
class _StraceFeature(Feature):
 
494
 
 
495
    def _probe(self):
 
496
        try:
 
497
            proc = subprocess.Popen(['strace'],
 
498
                                    stderr=subprocess.PIPE,
 
499
                                    stdout=subprocess.PIPE)
 
500
            proc.communicate()
 
501
            return True
 
502
        except OSError as e:
 
503
            import errno
 
504
            if e.errno == errno.ENOENT:
 
505
                # strace is not installed
 
506
                return False
 
507
            else:
 
508
                raise
 
509
 
 
510
    def feature_name(self):
 
511
        return 'strace'
 
512
 
 
513
 
 
514
strace_feature = _StraceFeature()
 
515
 
 
516
 
 
517
class _AttribFeature(Feature):
 
518
 
 
519
    def _probe(self):
 
520
        if (sys.platform not in ('cygwin', 'win32')):
 
521
            return False
 
522
        try:
 
523
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
 
524
        except OSError:
 
525
            return False
 
526
        return (0 == proc.wait())
 
527
 
 
528
    def feature_name(self):
 
529
        return 'attrib Windows command-line tool'
 
530
 
 
531
 
 
532
AttribFeature = _AttribFeature()
 
533
 
 
534
 
 
535
class Win32Feature(Feature):
 
536
    """Feature testing whether we're running selftest on Windows
 
537
    or Windows-like platform.
 
538
    """
 
539
 
 
540
    def _probe(self):
 
541
        return sys.platform == 'win32'
 
542
 
 
543
    def feature_name(self):
 
544
        return "win32 platform"
 
545
 
 
546
 
 
547
win32_feature = Win32Feature()
 
548
 
 
549
 
 
550
class _BackslashFilenameFeature(Feature):
 
551
    """Does the filesystem support backslashes in filenames?"""
 
552
 
 
553
    def _probe(self):
 
554
 
 
555
        try:
 
556
            fileno, name = tempfile.mkstemp(prefix='bzr\\prefix')
 
557
        except (IOError, OSError):
 
558
            return False
 
559
        else:
 
560
            try:
 
561
                os.stat(name)
 
562
            except (IOError, OSError):
 
563
                # mkstemp succeeded but the file wasn't actually created
 
564
                return False
 
565
            os.close(fileno)
 
566
            os.remove(name)
 
567
            return True
 
568
 
 
569
 
 
570
BackslashFilenameFeature = _BackslashFilenameFeature()