/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: John Arbash Meinel
  • Date: 2006-04-25 15:05:42 UTC
  • mfrom: (1185.85.85 bzr-encoding)
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: john@arbash-meinel.com-20060425150542-c7b518dca9928691
[merge] the old bzr-encoding changes, reparenting them on bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009, 2010, 2011 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
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
 
import os
24
 
import subprocess
25
 
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
 
 
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):
467
 
 
468
 
    def _probe(self):
469
 
        def has_perms():
470
 
            # Create temporary file and check if specified perms are
471
 
            # maintained.
472
 
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
473
 
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
474
 
            fd, name = f
475
 
            os.close(fd)
476
 
            osutils.chmod_if_possible(name, write_perms)
477
 
 
478
 
            read_perms = os.stat(name).st_mode & 0o777
479
 
            os.unlink(name)
480
 
            return (write_perms == read_perms)
481
 
 
482
 
        return (os.name == 'posix') and has_perms()
483
 
 
484
 
    def feature_name(self):
485
 
        return 'POSIX permissions support'
486
 
 
487
 
 
488
 
posix_permissions_feature = _PosixPermissionsFeature()
489
 
 
490
 
 
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