~bzr-pqm/bzr/bzr.dev

5557.1.15 by John Arbash Meinel
Merge bzr.dev 5597 to resolve NEWS, aka bzr-2.3.txt
1
# Copyright (C) 2009, 2010, 2011 Canonical Ltd
4584.3.21 by Martin Pool
Start adding tests for apport
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
5967.12.1 by Martin Pool
Move all test features into bzrlib.tests.features
17
"""A collection of commonly used 'Features' to optionally run tests.
18
"""
5241.2.1 by Robert Collins
Merge up from 2.0/2.1:
19
5036.3.8 by Parth Malwankar
closed review comments from vila
20
import os
6060.1.1 by John Arbash Meinel
import subprocess in 'bzrlib.tests.features' so that certain features can properly probe.
21
import subprocess
5036.3.8 by Parth Malwankar
closed review comments from vila
22
import stat
5609.47.1 by Alexander Belchenko
Win32Feature
23
import sys
5967.12.1 by Martin Pool
Move all test features into bzrlib.tests.features
24
import tempfile
4584.3.21 by Martin Pool
Start adding tests for apport
25
5321.2.1 by Vincent Ladeuil
Fix style issues, including vertical spaces, lines too long and multi lines imports.
26
from bzrlib import (
27
    osutils,
5967.12.1 by Martin Pool
Move all test features into bzrlib.tests.features
28
    symbol_versioning,
5321.2.1 by Vincent Ladeuil
Fix style issues, including vertical spaces, lines too long and multi lines imports.
29
    tests,
30
    )
4913.2.19 by John Arbash Meinel
Compatibly rename ApportFeature to features.apport.
31
32
5967.12.1 by Martin Pool
Move all test features into bzrlib.tests.features
33
class Feature(object):
34
    """An operating system Feature."""
35
36
    def __init__(self):
37
        self._available = None
38
39
    def available(self):
40
        """Is the feature available?
41
42
        :return: True if the feature is available.
43
        """
44
        if self._available is None:
45
            self._available = self._probe()
46
        return self._available
47
48
    def _probe(self):
49
        """Implement this method in concrete features.
50
51
        :return: True if the feature is available.
52
        """
53
        raise NotImplementedError
54
55
    def __str__(self):
56
        if getattr(self, 'feature_name', None):
57
            return self.feature_name()
58
        return self.__class__.__name__
59
60
61
class _SymlinkFeature(Feature):
62
63
    def _probe(self):
64
        return osutils.has_symlinks()
65
66
    def feature_name(self):
67
        return 'symlinks'
68
69
SymlinkFeature = _SymlinkFeature()
70
71
72
class _HardlinkFeature(Feature):
73
74
    def _probe(self):
75
        return osutils.has_hardlinks()
76
77
    def feature_name(self):
78
        return 'hardlinks'
79
80
HardlinkFeature = _HardlinkFeature()
81
82
83
class _OsFifoFeature(Feature):
84
85
    def _probe(self):
86
        return getattr(os, 'mkfifo', None)
87
88
    def feature_name(self):
89
        return 'filesystem fifos'
90
91
OsFifoFeature = _OsFifoFeature()
92
93
94
class _UnicodeFilenameFeature(Feature):
95
    """Does the filesystem support Unicode filenames?"""
96
97
    def _probe(self):
98
        try:
99
            # Check for character combinations unlikely to be covered by any
100
            # single non-unicode encoding. We use the characters
101
            # - greek small letter alpha (U+03B1) and
102
            # - braille pattern dots-123456 (U+283F).
103
            os.stat(u'\u03b1\u283f')
104
        except UnicodeEncodeError:
105
            return False
106
        except (IOError, OSError):
107
            # The filesystem allows the Unicode filename but the file doesn't
108
            # exist.
109
            return True
110
        else:
111
            # The filesystem allows the Unicode filename and the file exists,
112
            # for some reason.
113
            return True
114
115
UnicodeFilenameFeature = _UnicodeFilenameFeature()
116
117
118
class _CompatabilityThunkFeature(Feature):
119
    """This feature is just a thunk to another feature.
120
121
    It issues a deprecation warning if it is accessed, to let you know that you
122
    should really use a different feature.
123
    """
124
125
    def __init__(self, dep_version, module, name,
126
                 replacement_name, replacement_module=None):
127
        super(_CompatabilityThunkFeature, self).__init__()
128
        self._module = module
129
        if replacement_module is None:
130
            replacement_module = module
131
        self._replacement_module = replacement_module
132
        self._name = name
133
        self._replacement_name = replacement_name
134
        self._dep_version = dep_version
135
        self._feature = None
136
137
    def _ensure(self):
138
        if self._feature is None:
139
            from bzrlib import pyutils
140
            depr_msg = self._dep_version % ('%s.%s'
141
                                            % (self._module, self._name))
142
            use_msg = ' Use %s.%s instead.' % (self._replacement_module,
143
                                               self._replacement_name)
144
            symbol_versioning.warn(depr_msg + use_msg, DeprecationWarning)
145
            # Import the new feature and use it as a replacement for the
146
            # deprecated one.
147
            self._feature = pyutils.get_named_object(
148
                self._replacement_module, self._replacement_name)
149
150
    def _probe(self):
151
        self._ensure()
152
        return self._feature._probe()
153
154
155
class ModuleAvailableFeature(Feature):
156
    """This is a feature than describes a module we want to be available.
157
158
    Declare the name of the module in __init__(), and then after probing, the
159
    module will be available as 'self.module'.
160
161
    :ivar module: The module if it is available, else None.
162
    """
163
164
    def __init__(self, module_name):
165
        super(ModuleAvailableFeature, self).__init__()
166
        self.module_name = module_name
167
168
    def _probe(self):
169
        try:
170
            self._module = __import__(self.module_name, {}, {}, [''])
171
            return True
172
        except ImportError:
173
            return False
174
175
    @property
176
    def module(self):
177
        if self.available():
178
            return self._module
179
        return None
180
181
    def feature_name(self):
182
        return self.module_name
183
184
185
class _HTTPSServerFeature(Feature):
186
    """Some tests want an https Server, check if one is available.
187
188
    Right now, the only way this is available is under python2.6 which provides
189
    an ssl module.
190
    """
191
192
    def _probe(self):
193
        try:
194
            import ssl
195
            return True
196
        except ImportError:
197
            return False
198
199
    def feature_name(self):
200
        return 'HTTPSServer'
201
202
203
HTTPSServerFeature = _HTTPSServerFeature()
204
205
206
class _ByteStringNamedFilesystem(Feature):
207
    """Is the filesystem based on bytes?"""
208
209
    def _probe(self):
210
        if os.name == "posix":
211
            return True
212
        return False
213
214
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
215
216
217
class _UTF8Filesystem(Feature):
218
    """Is the filesystem UTF-8?"""
219
220
    def _probe(self):
221
        if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
222
            return True
223
        return False
224
225
UTF8Filesystem = _UTF8Filesystem()
226
227
228
class _BreakinFeature(Feature):
229
    """Does this platform support the breakin feature?"""
230
231
    def _probe(self):
232
        from bzrlib import breakin
233
        if breakin.determine_signal() is None:
234
            return False
235
        if sys.platform == 'win32':
236
            # Windows doesn't have os.kill, and we catch the SIGBREAK signal.
237
            # We trigger SIGBREAK via a Console api so we need ctypes to
238
            # access the function
239
            try:
240
                import ctypes
241
            except OSError:
242
                return False
243
        return True
244
245
    def feature_name(self):
246
        return "SIGQUIT or SIGBREAK w/ctypes on win32"
247
248
249
BreakinFeature = _BreakinFeature()
250
251
252
class _CaseInsCasePresFilenameFeature(Feature):
253
    """Is the file-system case insensitive, but case-preserving?"""
254
255
    def _probe(self):
256
        fileno, name = tempfile.mkstemp(prefix='MixedCase')
257
        try:
258
            # first check truly case-preserving for created files, then check
259
            # case insensitive when opening existing files.
260
            name = osutils.normpath(name)
261
            base, rel = osutils.split(name)
262
            found_rel = osutils.canonical_relpath(base, name)
263
            return (found_rel == rel
264
                    and os.path.isfile(name.upper())
265
                    and os.path.isfile(name.lower()))
266
        finally:
267
            os.close(fileno)
268
            os.remove(name)
269
270
    def feature_name(self):
271
        return "case-insensitive case-preserving filesystem"
272
273
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
274
275
276
class _CaseInsensitiveFilesystemFeature(Feature):
277
    """Check if underlying filesystem is case-insensitive but *not* case
278
    preserving.
279
    """
280
    # Note that on Windows, Cygwin, MacOS etc, the file-systems are far
281
    # more likely to be case preserving, so this case is rare.
282
283
    def _probe(self):
284
        if CaseInsCasePresFilenameFeature.available():
285
            return False
286
287
        if tests.TestCaseWithMemoryTransport.TEST_ROOT is None:
288
            root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
289
            tests.TestCaseWithMemoryTransport.TEST_ROOT = root
290
        else:
291
            root = tests.TestCaseWithMemoryTransport.TEST_ROOT
292
        tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
293
            dir=root)
294
        name_a = osutils.pathjoin(tdir, 'a')
295
        name_A = osutils.pathjoin(tdir, 'A')
296
        os.mkdir(name_a)
297
        result = osutils.isdir(name_A)
298
        tests._rmtree_temp_dir(tdir)
299
        return result
300
301
    def feature_name(self):
302
        return 'case-insensitive filesystem'
303
304
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
305
306
307
class _CaseSensitiveFilesystemFeature(Feature):
308
309
    def _probe(self):
310
        if CaseInsCasePresFilenameFeature.available():
311
            return False
312
        elif CaseInsensitiveFilesystemFeature.available():
313
            return False
314
        else:
315
            return True
316
317
    def feature_name(self):
318
        return 'case-sensitive filesystem'
319
320
# new coding style is for feature instances to be lowercase
321
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
322
323
324
class _NotRunningAsRoot(Feature):
4797.70.1 by Vincent Ladeuil
Skip chmodbits dependent tests when running as root
325
326
    def _probe(self):
327
        try:
328
            uid = os.getuid()
329
        except AttributeError:
330
            # If there is no uid, chances are there is no root either
331
            return True
332
        return uid != 0
333
334
    def feature_name(self):
335
        return 'Not running as root'
336
337
338
not_running_as_root = _NotRunningAsRoot()
5448.5.9 by Vincent Ladeuil
Make the test depends on a feature so it's skipped if meliae is not installed
339
5967.12.1 by Martin Pool
Move all test features into bzrlib.tests.features
340
apport = ModuleAvailableFeature('apport')
5967.12.2 by Martin Pool
Move all features to bzrlib.tests.features in 2.5
341
gpgme = ModuleAvailableFeature('gpgme')
5967.12.1 by Martin Pool
Move all test features into bzrlib.tests.features
342
lzma = ModuleAvailableFeature('lzma')
343
meliae = ModuleAvailableFeature('meliae')
344
paramiko = ModuleAvailableFeature('paramiko')
345
pycurl = ModuleAvailableFeature('pycurl')
346
pywintypes = ModuleAvailableFeature('pywintypes')
347
sphinx = ModuleAvailableFeature('sphinx')
348
subunit = ModuleAvailableFeature('subunit')
5967.12.3 by Martin Pool
Unify duplicated UnicodeFilename and _PosixPermissionsFeature
349
testtools = ModuleAvailableFeature('testtools')
5967.12.1 by Martin Pool
Move all test features into bzrlib.tests.features
350
351
compiled_patiencediff_feature = ModuleAvailableFeature(
352
    'bzrlib._patiencediff_c')
353
meliae_feature = ModuleAvailableFeature('meliae.scanner')
354
lsprof_feature = ModuleAvailableFeature('bzrlib.lsprof')
355
356
357
class _BackslashDirSeparatorFeature(Feature):
5241.2.1 by Robert Collins
Merge up from 2.0/2.1:
358
359
    def _probe(self):
360
        try:
361
            os.lstat(os.getcwd() + '\\')
362
        except OSError:
363
            return False
364
        else:
365
            return True
366
367
    def feature_name(self):
368
        return "Filesystem treats '\\' as a directory separator."
369
370
backslashdir_feature = _BackslashDirSeparatorFeature()
371
372
5967.12.1 by Martin Pool
Move all test features into bzrlib.tests.features
373
class _ChownFeature(Feature):
5051.4.10 by Parth Malwankar
moved ChownFeature to tests/features.py
374
    """os.chown is supported"""
375
376
    def _probe(self):
377
        return os.name == 'posix' and hasattr(os, 'chown')
378
5051.4.11 by Parth Malwankar
closed Martins review comments.
379
chown_feature = _ChownFeature()
5051.4.10 by Parth Malwankar
moved ChownFeature to tests/features.py
380
5147.5.20 by Martin von Gagern
Move ExecutableFeature class from bash_completion plugin to bzrlib tests.
381
5967.12.1 by Martin Pool
Move all test features into bzrlib.tests.features
382
class ExecutableFeature(Feature):
5147.5.20 by Martin von Gagern
Move ExecutableFeature class from bash_completion plugin to bzrlib tests.
383
    """Feature testing whether an executable of a given name is on the PATH."""
384
385
    def __init__(self, name):
386
        super(ExecutableFeature, self).__init__()
387
        self.name = name
5147.5.23 by Martin von Gagern
Have ExecutableFeature implement and use _probe.
388
        self._path = None
5147.5.20 by Martin von Gagern
Move ExecutableFeature class from bash_completion plugin to bzrlib tests.
389
390
    @property
391
    def path(self):
5147.5.23 by Martin von Gagern
Have ExecutableFeature implement and use _probe.
392
        # This is a property, so accessing path ensures _probe was called
393
        self.available()
394
        return self._path
5147.5.20 by Martin von Gagern
Move ExecutableFeature class from bash_completion plugin to bzrlib tests.
395
5147.5.23 by Martin von Gagern
Have ExecutableFeature implement and use _probe.
396
    def _probe(self):
5321.1.82 by Gordon Tyler
Changed ExecutableFeature to use osutils.find_executable_on_path.
397
        self._path = osutils.find_executable_on_path(self.name)
398
        return self._path is not None
5147.5.20 by Martin von Gagern
Move ExecutableFeature class from bash_completion plugin to bzrlib tests.
399
400
    def feature_name(self):
401
        return '%s executable' % self.name
5147.5.24 by Martin von Gagern
Move ExecutableFeature instances to tests.features module.
402
403
404
bash_feature = ExecutableFeature('bash')
405
sed_feature = ExecutableFeature('sed')
5349.1.5 by Matthäus G. Chajdas
Fix issues raised by Vincent Ladeuil.
406
diff_feature = ExecutableFeature('diff')
5609.47.1 by Alexander Belchenko
Win32Feature
407
408
5967.12.1 by Martin Pool
Move all test features into bzrlib.tests.features
409
class _PosixPermissionsFeature(Feature):
410
411
    def _probe(self):
412
        def has_perms():
413
            # Create temporary file and check if specified perms are
414
            # maintained.
415
            write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
416
            f = tempfile.mkstemp(prefix='bzr_perms_chk_')
417
            fd, name = f
418
            os.close(fd)
419
            os.chmod(name, write_perms)
420
421
            read_perms = os.stat(name).st_mode & 0777
422
            os.unlink(name)
423
            return (write_perms == read_perms)
424
425
        return (os.name == 'posix') and has_perms()
426
427
    def feature_name(self):
428
        return 'POSIX permissions support'
429
430
431
posix_permissions_feature = _PosixPermissionsFeature()
432
433
434
class _StraceFeature(Feature):
435
436
    def _probe(self):
437
        try:
438
            proc = subprocess.Popen(['strace'],
439
                stderr=subprocess.PIPE,
440
                stdout=subprocess.PIPE)
441
            proc.communicate()
442
            return True
443
        except OSError, e:
444
            if e.errno == errno.ENOENT:
445
                # strace is not installed
446
                return False
447
            else:
448
                raise
449
450
    def feature_name(self):
451
        return 'strace'
452
453
454
strace_feature = _StraceFeature()
455
456
457
class _AttribFeature(Feature):
458
459
    def _probe(self):
460
        if (sys.platform not in ('cygwin', 'win32')):
461
            return False
462
        try:
463
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
464
        except OSError, e:
465
            return False
466
        return (0 == proc.wait())
467
468
    def feature_name(self):
469
        return 'attrib Windows command-line tool'
470
471
472
AttribFeature = _AttribFeature()
5967.12.2 by Martin Pool
Move all features to bzrlib.tests.features in 2.5
473
474
5967.12.3 by Martin Pool
Unify duplicated UnicodeFilename and _PosixPermissionsFeature
475
class Win32Feature(Feature):
5609.47.1 by Alexander Belchenko
Win32Feature
476
    """Feature testing whether we're running selftest on Windows
5609.47.4 by Alexander Belchenko
fixed typo
477
    or Windows-like platform.
5609.47.1 by Alexander Belchenko
Win32Feature
478
    """
479
480
    def _probe(self):
481
        return sys.platform == 'win32'
482
483
    def feature_name(self):
484
        return "win32 platform"
485
5967.12.4 by Martin Pool
Support (but deprecated) old feature names
486
5609.47.1 by Alexander Belchenko
Win32Feature
487
win32_feature = Win32Feature()
5967.12.4 by Martin Pool
Support (but deprecated) old feature names
488
489
490
for name in ['HTTPServerFeature', 
491
    'HTTPSServerFeature', 'SymlinkFeature', 'HardlinkFeature',
492
    'OsFifoFeature', 'UnicodeFilenameFeature',
493
    'ByteStringNamedFilesystem', 'UTF8Filesystem',
494
    'BreakinFeature', 'CaseInsCasePresFilenameFeature',
495
    'CaseInsensitiveFilesystemFeature', 'case_sensitive_filesystem_feature',
496
    'posix_permissions_feature',
497
    ]:
498
    setattr(tests, name, _CompatabilityThunkFeature(
6034.2.3 by Martin Pool
bzrlib.tests.Feature moved to tests.features in 2.5 not 2.4
499
        symbol_versioning.deprecated_in((2, 5, 0)),
5967.12.4 by Martin Pool
Support (but deprecated) old feature names
500
        'bzrlib.tests', name,
501
        name, 'bzrlib.tests.features'))
502
503
504
for (old_name, new_name) in [
505
    ('UnicodeFilename', 'UnicodeFilenameFeature'),
506
    ]:
507
    setattr(tests, name, _CompatabilityThunkFeature(
6034.2.3 by Martin Pool
bzrlib.tests.Feature moved to tests.features in 2.5 not 2.4
508
        symbol_versioning.deprecated_in((2, 5, 0)),
5967.12.4 by Martin Pool
Support (but deprecated) old feature names
509
        'bzrlib.tests', old_name,
510
        new_name, 'bzrlib.tests.features'))