/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: Robert Collins
  • Date: 2005-12-24 02:20:45 UTC
  • mto: (1185.50.57 bzr-jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1550.
  • Revision ID: robertc@robertcollins.net-20051224022045-14efc8dfa0e1a4e9
Start tests for api usage.

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
 
import importlib
21
 
import os
22
 
import subprocess
23
 
import stat
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):
465
 
 
466
 
    def _probe(self):
467
 
        def has_perms():
468
 
            # Create temporary file and check if specified perms are
469
 
            # maintained.
470
 
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
471
 
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
472
 
            fd, name = f
473
 
            os.close(fd)
474
 
            osutils.chmod_if_possible(name, write_perms)
475
 
 
476
 
            read_perms = os.stat(name).st_mode & 0o777
477
 
            os.unlink(name)
478
 
            return (write_perms == read_perms)
479
 
 
480
 
        return (os.name == 'posix') and has_perms()
481
 
 
482
 
    def feature_name(self):
483
 
        return 'POSIX permissions support'
484
 
 
485
 
 
486
 
posix_permissions_feature = _PosixPermissionsFeature()
487
 
 
488
 
 
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