/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: Martin
  • Date: 2018-11-16 16:38:22 UTC
  • mto: This revision was merged to the branch mainline in revision 7172.
  • Revision ID: gzlist@googlemail.com-20181116163822-yg1h1cdng6w7w9kn
Make --profile-imports work on Python 3

Also tweak heading to line up correctly.

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