/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 bzrlib/tests/features.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009, 2010, 2011 Canonical Ltd
 
1
# Copyright (C) 2009, 2010 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
23
17
import os
24
 
import subprocess
25
18
import stat
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):
 
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):
456
31
 
457
32
    def _probe(self):
458
33
        def has_perms():
459
 
            # Create temporary file and check if specified perms are
460
 
            # maintained.
 
34
            # create temporary file and check if specified perms are maintained.
 
35
            import tempfile
 
36
 
461
37
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
462
38
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
463
39
            fd, name = f
464
40
            os.close(fd)
465
 
            osutils.chmod_if_possible(name, write_perms)
 
41
            os.chmod(name, write_perms)
466
42
 
467
 
            read_perms = os.stat(name).st_mode & 0o777
 
43
            read_perms = os.stat(name).st_mode & 0777
468
44
            os.unlink(name)
469
45
            return (write_perms == read_perms)
470
46
 
477
53
posix_permissions_feature = _PosixPermissionsFeature()
478
54
 
479
55
 
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()
 
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