~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_errors.py

  • Committer: Vincent Ladeuil
  • Date: 2007-09-24 15:01:26 UTC
  • mfrom: (2853 +trunk)
  • mto: (2885.1.1 140432)
  • mto: This revision was merged to the branch mainline in revision 2886.
  • Revision ID: v.ladeuil+lp@free.fr-20070924150126-nll7i0385kisklyj
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
2
#   Authors: Robert Collins <robert.collins@canonical.com>
 
3
#            and others
2
4
#
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
12
14
#
13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
18
 
17
19
"""Tests for the formatting and construction of errors."""
18
20
 
19
 
import inspect
20
 
import re
21
 
import socket
22
 
import sys
23
 
 
24
21
from bzrlib import (
25
22
    bzrdir,
26
23
    errors,
27
 
    osutils,
28
 
    symbol_versioning,
29
 
    urlutils,
30
24
    )
31
 
from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
 
25
from bzrlib.tests import TestCase, TestCaseWithTransport
 
26
 
 
27
 
 
28
# TODO: Make sure builtin exception class formats are consistent - e.g. should
 
29
# or shouldn't end with a full stop, etc.
32
30
 
33
31
 
34
32
class TestErrors(TestCaseWithTransport):
35
33
 
36
 
    def test_no_arg_named_message(self):
37
 
        """Ensure the __init__ and _fmt in errors do not have "message" arg.
38
 
 
39
 
        This test fails if __init__ or _fmt in errors has an argument
40
 
        named "message" as this can cause errors in some Python versions.
41
 
        Python 2.5 uses a slot for StandardError.message.
42
 
        See bug #603461
43
 
        """
44
 
        fmt_pattern = re.compile("%\(message\)[sir]")
45
 
        subclasses_present = getattr(errors.BzrError, '__subclasses__', None)
46
 
        if not subclasses_present:
47
 
            raise TestSkipped('__subclasses__ attribute required for classes. '
48
 
                'Requires Python 2.5 or later.')
49
 
        for c in errors.BzrError.__subclasses__():
50
 
            init = getattr(c, '__init__', None)
51
 
            fmt = getattr(c, '_fmt', None)
52
 
            if init:
53
 
                args = inspect.getargspec(init)[0]
54
 
                self.assertFalse('message' in args,
55
 
                    ('Argument name "message" not allowed for '
56
 
                    '"errors.%s.__init__"' % c.__name__))
57
 
            if fmt and fmt_pattern.search(fmt):
58
 
                self.assertFalse(True, ('"message" not allowed in '
59
 
                    '"errors.%s._fmt"' % c.__name__))
60
 
 
61
 
    def test_bad_filename_encoding(self):
62
 
        error = errors.BadFilenameEncoding('bad/filen\xe5me', 'UTF-8')
63
 
        self.assertEqualDiff(
64
 
            "Filename 'bad/filen\\xe5me' is not valid in your current"
65
 
            " filesystem encoding UTF-8",
66
 
            str(error))
67
 
 
68
 
    def test_corrupt_dirstate(self):
69
 
        error = errors.CorruptDirstate('path/to/dirstate', 'the reason why')
70
 
        self.assertEqualDiff(
71
 
            "Inconsistency in dirstate file path/to/dirstate.\n"
72
 
            "Error: the reason why",
73
 
            str(error))
74
 
 
75
 
    def test_dirstate_corrupt(self):
76
 
        error = errors.DirstateCorrupt('.bzr/checkout/dirstate',
77
 
                                       'trailing garbage: "x"')
78
 
        self.assertEqualDiff("The dirstate file (.bzr/checkout/dirstate)"
79
 
            " appears to be corrupt: trailing garbage: \"x\"",
80
 
            str(error))
81
 
 
82
34
    def test_disabled_method(self):
83
35
        error = errors.DisabledMethod("class name")
84
36
        self.assertEqualDiff(
94
46
        self.assertEqualDiff('The prefix foo is in the help search path twice.',
95
47
            str(error))
96
48
 
97
 
    def test_ghost_revisions_have_no_revno(self):
98
 
        error = errors.GhostRevisionsHaveNoRevno('target', 'ghost_rev')
99
 
        self.assertEqualDiff("Could not determine revno for {target} because"
100
 
                             " its ancestry shows a ghost at {ghost_rev}",
101
 
                             str(error))
102
 
 
103
49
    def test_incompatibleAPI(self):
104
50
        error = errors.IncompatibleAPI("module", (1, 2, 3), (4, 5, 6), (7, 8, 9))
105
51
        self.assertEqualDiff(
107
53
            'It supports versions "(4, 5, 6)" to "(7, 8, 9)".',
108
54
            str(error))
109
55
 
110
 
    def test_inconsistent_delta(self):
111
 
        error = errors.InconsistentDelta('path', 'file-id', 'reason for foo')
112
 
        self.assertEqualDiff(
113
 
            "An inconsistent delta was supplied involving 'path', 'file-id'\n"
114
 
            "reason: reason for foo",
115
 
            str(error))
116
 
 
117
 
    def test_inconsistent_delta_delta(self):
118
 
        error = errors.InconsistentDeltaDelta([], 'reason')
119
 
        self.assertEqualDiff(
120
 
            "An inconsistent delta was supplied: []\nreason: reason",
121
 
            str(error))
122
 
 
123
56
    def test_in_process_transport(self):
124
57
        error = errors.InProcessTransport('fpp')
125
58
        self.assertEqualDiff(
126
59
            "The transport 'fpp' is only accessible within this process.",
127
60
            str(error))
128
61
 
129
 
    def test_invalid_http_range(self):
130
 
        error = errors.InvalidHttpRange('path',
131
 
                                        'Content-Range: potatoes 0-00/o0oo0',
132
 
                                        'bad range')
133
 
        self.assertEquals("Invalid http range"
134
 
                          " 'Content-Range: potatoes 0-00/o0oo0'"
135
 
                          " for path: bad range",
136
 
                          str(error))
137
 
 
138
 
    def test_invalid_range(self):
139
 
        error = errors.InvalidRange('path', 12, 'bad range')
140
 
        self.assertEquals("Invalid range access in path at 12: bad range",
141
 
                          str(error))
142
 
 
143
62
    def test_inventory_modified(self):
144
63
        error = errors.InventoryModified("a tree to be repred")
145
64
        self.assertEqualDiff("The current inventory for the tree 'a tree to "
147
66
            "read without data loss.",
148
67
            str(error))
149
68
 
150
 
    def test_jail_break(self):
151
 
        error = errors.JailBreak("some url")
152
 
        self.assertEqualDiff("An attempt to access a url outside the server"
153
 
            " jail was made: 'some url'.",
154
 
            str(error))
 
69
    def test_install_failed(self):
 
70
        error = errors.InstallFailed(['rev-one'])
 
71
        self.assertEqual("Could not install revisions:\nrev-one", str(error))
 
72
        error = errors.InstallFailed(['rev-one', 'rev-two'])
 
73
        self.assertEqual("Could not install revisions:\nrev-one, rev-two",
 
74
                         str(error))
 
75
        error = errors.InstallFailed([None])
 
76
        self.assertEqual("Could not install revisions:\nNone", str(error))
155
77
 
156
78
    def test_lock_active(self):
157
79
        error = errors.LockActive("lock description")
166
88
                         '"stream format" into knit of format '
167
89
                         '"target format".', str(error))
168
90
 
169
 
    def test_knit_data_stream_unknown(self):
170
 
        error = errors.KnitDataStreamUnknown(
171
 
            'stream format')
172
 
        self.assertEqual('Cannot parse knit data stream of format '
173
 
                         '"stream format".', str(error))
174
 
 
175
91
    def test_knit_header_error(self):
176
92
        error = errors.KnitHeaderError('line foo\n', 'path/to/file')
177
93
        self.assertEqual("Knit header error: 'line foo\\n' unexpected"
188
104
        error = errors.MediumNotConnected("a medium")
189
105
        self.assertEqualDiff(
190
106
            "The medium 'a medium' is not connected.", str(error))
191
 
 
192
 
    def test_no_public_branch(self):
193
 
        b = self.make_branch('.')
194
 
        error = errors.NoPublicBranch(b)
195
 
        url = urlutils.unescape_for_display(b.base, 'ascii')
196
 
        self.assertEqualDiff(
197
 
            'There is no public branch set for "%s".' % url, str(error))
198
 
 
 
107
        
199
108
    def test_no_repo(self):
200
109
        dir = bzrdir.BzrDir.create(self.get_url())
201
110
        error = errors.NoRepositoryPresent(dir)
202
111
        self.assertNotEqual(-1, str(error).find((dir.transport.clone('..').base)))
203
112
        self.assertEqual(-1, str(error).find((dir.transport.base)))
204
 
 
 
113
        
205
114
    def test_no_smart_medium(self):
206
115
        error = errors.NoSmartMedium("a transport")
207
116
        self.assertEqualDiff("The transport 'a transport' cannot tunnel the "
226
135
                             " tree atree.", str(error))
227
136
        self.assertIsInstance(error, errors.NoSuchRevision)
228
137
 
229
 
    def test_not_stacked(self):
230
 
        error = errors.NotStacked('a branch')
231
 
        self.assertEqualDiff("The branch 'a branch' is not stacked.",
232
 
            str(error))
233
 
 
234
138
    def test_not_write_locked(self):
235
139
        error = errors.NotWriteLocked('a thing to repr')
236
140
        self.assertEqualDiff("'a thing to repr' is not write locked but needs "
237
141
            "to be.",
238
142
            str(error))
239
143
 
240
 
    def test_lock_failed(self):
241
 
        error = errors.LockFailed('http://canonical.com/', 'readonly transport')
242
 
        self.assertEqualDiff("Cannot lock http://canonical.com/: readonly transport",
243
 
            str(error))
244
 
        self.assertFalse(error.internal_error)
 
144
    def test_read_only_lock_error(self):
 
145
        error = errors.ReadOnlyLockError('filename', 'error message')
 
146
        self.assertEqualDiff("Cannot acquire write lock on filename."
 
147
                             " error message", str(error))
245
148
 
246
149
    def test_too_many_concurrent_requests(self):
247
150
        error = errors.TooManyConcurrentRequests("a medium")
250
153
            "the currently open request.",
251
154
            str(error))
252
155
 
253
 
    def test_unavailable_representation(self):
254
 
        error = errors.UnavailableRepresentation(('key',), "mpdiff", "fulltext")
255
 
        self.assertEqualDiff("The encoding 'mpdiff' is not available for key "
256
 
            "('key',) which is encoded as 'fulltext'.",
257
 
            str(error))
258
 
 
259
156
    def test_unknown_hook(self):
260
157
        error = errors.UnknownHook("branch", "foo")
261
158
        self.assertEqualDiff("The branch hook 'foo' is unknown in this version"
266
163
            " of bzrlib.",
267
164
            str(error))
268
165
 
269
 
    def test_unstackable_branch_format(self):
270
 
        format = u'foo'
271
 
        url = "/foo"
272
 
        error = errors.UnstackableBranchFormat(format, url)
273
 
        self.assertEqualDiff(
274
 
            "The branch '/foo'(foo) is not a stackable format. "
275
 
            "You will need to upgrade the branch to permit branch stacking.",
276
 
            str(error))
277
 
 
278
 
    def test_unstackable_location(self):
279
 
        error = errors.UnstackableLocationError('foo', 'bar')
280
 
        self.assertEqualDiff("The branch 'foo' cannot be stacked on 'bar'.",
281
 
            str(error))
282
 
 
283
 
    def test_unstackable_repository_format(self):
284
 
        format = u'foo'
285
 
        url = "/foo"
286
 
        error = errors.UnstackableRepositoryFormat(format, url)
287
 
        self.assertEqualDiff(
288
 
            "The repository '/foo'(foo) is not a stackable format. "
289
 
            "You will need to upgrade the repository to permit branch stacking.",
290
 
            str(error))
291
 
 
292
166
    def test_up_to_date(self):
293
167
        error = errors.UpToDateFormat(bzrdir.BzrDirFormat4())
294
 
        self.assertEqualDiff("The branch format All-in-one "
295
 
                             "format 4 is already at the most "
 
168
        self.assertEqualDiff("The branch format Bazaar-NG branch, "
 
169
                             "format 0.0.4 is already at the most "
296
170
                             "recent format.",
297
171
                             str(error))
298
172
 
426
300
            host='ahost', port=444, msg='Unable to connect to ssh host',
427
301
            orig_error='my_error')
428
302
 
429
 
    def test_target_not_branch(self):
430
 
        """Test the formatting of TargetNotBranch."""
431
 
        error = errors.TargetNotBranch('foo')
432
 
        self.assertEqual(
433
 
            "Your branch does not have all of the revisions required in "
434
 
            "order to merge this merge directive and the target "
435
 
            "location specified in the merge directive is not a branch: "
436
 
            "foo.", str(error))
437
 
 
438
303
    def test_malformed_bug_identifier(self):
439
304
        """Test the formatting of MalformedBugIdentifier."""
440
305
        error = errors.MalformedBugIdentifier('bogus', 'reason for bogosity')
441
306
        self.assertEqual(
442
 
            'Did not understand bug identifier bogus: reason for bogosity. '
443
 
            'See "bzr help bugs" for more information on this feature.',
 
307
            "Did not understand bug identifier bogus: reason for bogosity",
444
308
            str(error))
445
309
 
446
310
    def test_unknown_bug_tracker_abbreviation(self):
498
362
            "Container has multiple records with the same name: n\xc3\xa5me",
499
363
            str(e))
500
364
 
501
 
    def test_check_error(self):
502
 
        # This has a member called 'message', which is problematic in
503
 
        # python2.5 because that is a slot on the base Exception class
504
 
        e = errors.BzrCheckError('example check failure')
505
 
        self.assertEqual(
506
 
            "Internal check failed: example check failure",
507
 
            str(e))
508
 
        self.assertTrue(e.internal_error)
509
 
 
510
 
    def test_repository_data_stream_error(self):
511
 
        """Test the formatting of RepositoryDataStreamError."""
512
 
        e = errors.RepositoryDataStreamError(u"my reason")
513
 
        self.assertEqual(
514
 
            "Corrupt or incompatible data stream: my reason", str(e))
515
 
 
516
 
    def test_immortal_pending_deletion_message(self):
517
 
        err = errors.ImmortalPendingDeletion('foo')
518
 
        self.assertEquals(
519
 
            "Unable to delete transform temporary directory foo.  "
520
 
            "Please examine foo to see if it contains any files "
521
 
            "you wish to keep, and delete it when you are done.",
522
 
            str(err))
523
 
 
524
 
    def test_unable_create_symlink(self):
525
 
        err = errors.UnableCreateSymlink()
526
 
        self.assertEquals(
527
 
            "Unable to create symlink on this platform",
528
 
            str(err))
529
 
        err = errors.UnableCreateSymlink(path=u'foo')
530
 
        self.assertEquals(
531
 
            "Unable to create symlink 'foo' on this platform",
532
 
            str(err))
533
 
        err = errors.UnableCreateSymlink(path=u'\xb5')
534
 
        self.assertEquals(
535
 
            "Unable to create symlink u'\\xb5' on this platform",
536
 
            str(err))
537
 
 
538
 
    def test_invalid_url_join(self):
539
 
        """Test the formatting of InvalidURLJoin."""
540
 
        e = errors.InvalidURLJoin('Reason', 'base path', ('args',))
541
 
        self.assertEqual(
542
 
            "Invalid URL join request: Reason: 'base path' + ('args',)",
543
 
            str(e))
544
 
 
545
 
    def test_incorrect_url(self):
546
 
        err = errors.InvalidBugTrackerURL('foo', 'http://bug.com/')
547
 
        self.assertEquals(
548
 
            ("The URL for bug tracker \"foo\" doesn't contain {id}: "
549
 
             "http://bug.com/"),
550
 
            str(err))
551
 
 
552
 
    def test_unable_encode_path(self):
553
 
        err = errors.UnableEncodePath('foo', 'executable')
554
 
        self.assertEquals("Unable to encode executable path 'foo' in "
555
 
            "user encoding " + osutils.get_user_encoding(),
556
 
            str(err))
557
 
 
558
 
    def test_unknown_format(self):
559
 
        err = errors.UnknownFormatError('bar', kind='foo')
560
 
        self.assertEquals("Unknown foo format: 'bar'", str(err))
561
 
 
562
 
    def test_unknown_rules(self):
563
 
        err = errors.UnknownRules(['foo', 'bar'])
564
 
        self.assertEquals("Unknown rules detected: foo, bar.", str(err))
565
 
 
566
 
    def test_hook_failed(self):
567
 
        # Create an exc_info tuple by raising and catching an exception.
568
 
        try:
569
 
            1/0
570
 
        except ZeroDivisionError:
571
 
            exc_info = sys.exc_info()
572
 
        err = errors.HookFailed('hook stage', 'hook name', exc_info, warn=False)
573
 
        self.assertStartsWith(
574
 
            str(err), 'Hook \'hook name\' during hook stage failed:\n')
575
 
        self.assertEndsWith(
576
 
            str(err), 'integer division or modulo by zero')
577
 
 
578
 
    def test_tip_change_rejected(self):
579
 
        err = errors.TipChangeRejected(u'Unicode message\N{INTERROBANG}')
580
 
        self.assertEquals(
581
 
            u'Tip change rejected: Unicode message\N{INTERROBANG}',
582
 
            unicode(err))
583
 
        self.assertEquals(
584
 
            'Tip change rejected: Unicode message\xe2\x80\xbd',
585
 
            str(err))
586
 
 
587
 
    def test_error_from_smart_server(self):
588
 
        error_tuple = ('error', 'tuple')
589
 
        err = errors.ErrorFromSmartServer(error_tuple)
590
 
        self.assertEquals(
591
 
            "Error received from smart server: ('error', 'tuple')", str(err))
592
 
 
593
 
    def test_untranslateable_error_from_smart_server(self):
594
 
        error_tuple = ('error', 'tuple')
595
 
        orig_err = errors.ErrorFromSmartServer(error_tuple)
596
 
        err = errors.UnknownErrorFromSmartServer(orig_err)
597
 
        self.assertEquals(
598
 
            "Server sent an unexpected error: ('error', 'tuple')", str(err))
599
 
 
600
 
    def test_smart_message_handler_error(self):
601
 
        # Make an exc_info tuple.
602
 
        try:
603
 
            raise Exception("example error")
604
 
        except Exception:
605
 
            exc_info = sys.exc_info()
606
 
        err = errors.SmartMessageHandlerError(exc_info)
607
 
        self.assertStartsWith(
608
 
            str(err), "The message handler raised an exception:\n")
609
 
        self.assertEndsWith(str(err), "Exception: example error\n")
610
 
 
611
 
    def test_must_have_working_tree(self):
612
 
        err = errors.MustHaveWorkingTree('foo', 'bar')
613
 
        self.assertEqual(str(err), "Branching 'bar'(foo) must create a"
614
 
                                   " working tree.")
615
 
 
616
 
    def test_no_such_view(self):
617
 
        err = errors.NoSuchView('foo')
618
 
        self.assertEquals("No such view: foo.", str(err))
619
 
 
620
 
    def test_views_not_supported(self):
621
 
        err = errors.ViewsNotSupported('atree')
622
 
        err_str = str(err)
623
 
        self.assertStartsWith(err_str, "Views are not supported by ")
624
 
        self.assertEndsWith(err_str, "; use 'bzr upgrade' to change your "
625
 
            "tree to a later format.")
626
 
 
627
 
    def test_file_outside_view(self):
628
 
        err = errors.FileOutsideView('baz', ['foo', 'bar'])
629
 
        self.assertEquals('Specified file "baz" is outside the current view: '
630
 
            'foo, bar', str(err))
631
 
 
632
 
    def test_invalid_shelf_id(self):
633
 
        invalid_id = "foo"
634
 
        err = errors.InvalidShelfId(invalid_id)
635
 
        self.assertEqual('"foo" is not a valid shelf id, '
636
 
            'try a number instead.', str(err))
637
 
 
638
 
    def test_unresumable_write_group(self):
639
 
        repo = "dummy repo"
640
 
        wg_tokens = ['token']
641
 
        reason = "a reason"
642
 
        err = errors.UnresumableWriteGroup(repo, wg_tokens, reason)
643
 
        self.assertEqual(
644
 
            "Repository dummy repo cannot resume write group "
645
 
            "['token']: a reason", str(err))
646
 
 
647
 
    def test_unsuspendable_write_group(self):
648
 
        repo = "dummy repo"
649
 
        err = errors.UnsuspendableWriteGroup(repo)
650
 
        self.assertEqual(
651
 
            'Repository dummy repo cannot suspend a write group.', str(err))
652
 
 
653
 
    def test_not_branch_no_args(self):
654
 
        err = errors.NotBranchError('path')
655
 
        self.assertEqual('Not a branch: "path".', str(err))
656
 
 
657
 
    def test_not_branch_bzrdir_with_repo(self):
658
 
        bzrdir = self.make_repository('repo').bzrdir
659
 
        err = errors.NotBranchError('path', bzrdir=bzrdir)
660
 
        self.assertEqual(
661
 
            'Not a branch: "path": location is a repository.', str(err))
662
 
 
663
 
    def test_not_branch_bzrdir_without_repo(self):
664
 
        bzrdir = self.make_bzrdir('bzrdir')
665
 
        err = errors.NotBranchError('path', bzrdir=bzrdir)
666
 
        self.assertEqual('Not a branch: "path".', str(err))
667
 
 
668
 
    def test_not_branch_laziness(self):
669
 
        real_bzrdir = self.make_bzrdir('path')
670
 
        class FakeBzrDir(object):
671
 
            def __init__(self):
672
 
                self.calls = []
673
 
            def open_repository(self):
674
 
                self.calls.append('open_repository')
675
 
                raise errors.NoRepositoryPresent(real_bzrdir)
676
 
        fake_bzrdir = FakeBzrDir()
677
 
        err = errors.NotBranchError('path', bzrdir=fake_bzrdir)
678
 
        self.assertEqual([], fake_bzrdir.calls)
679
 
        str(err)
680
 
        self.assertEqual(['open_repository'], fake_bzrdir.calls)
681
 
        # Stringifying twice doesn't try to open a repository twice.
682
 
        str(err)
683
 
        self.assertEqual(['open_repository'], fake_bzrdir.calls)
684
 
 
685
 
    def test_invalid_pattern(self):
686
 
        error = errors.InvalidPattern('Bad pattern msg.')
687
 
        self.assertEqualDiff("Invalid pattern(s) found. Bad pattern msg.",
688
 
            str(error))
689
 
 
690
 
    def test_recursive_bind(self):
691
 
        error = errors.RecursiveBind('foo_bar_branch')
692
 
        msg = ('Branch "foo_bar_branch" appears to be bound to itself. '
693
 
            'Please use `bzr unbind` to fix.')
694
 
        self.assertEqualDiff(msg, str(error))
695
 
 
696
365
 
697
366
class PassThroughError(errors.BzrError):
698
 
 
 
367
    
699
368
    _fmt = """Pass through %(foo)s and %(bar)s"""
700
369
 
701
370
    def __init__(self, foo, bar):
708
377
 
709
378
 
710
379
class ErrorWithNoFormat(errors.BzrError):
711
 
    __doc__ = """This class has a docstring but no format string."""
 
380
    """This class has a docstring but no format string."""
712
381
 
713
382
 
714
383
class TestErrorFormatting(TestCase):
715
 
 
 
384
    
716
385
    def test_always_str(self):
717
386
        e = PassThroughError(u'\xb5', 'bar')
718
387
        self.assertIsInstance(e.__str__(), str)
729
398
                ['ErrorWithNoFormat uses its docstring as a format, it should use _fmt instead'],
730
399
                lambda x: str(x), e)
731
400
        ## s = str(e)
732
 
        self.assertEqual(s,
 
401
        self.assertEqual(s, 
733
402
                "This class has a docstring but no format string.")
734
403
 
735
404
    def test_mismatched_format_args(self):
739
408
        e = ErrorWithBadFormat(not_thing='x')
740
409
        self.assertStartsWith(
741
410
            str(e), 'Unprintable exception ErrorWithBadFormat')
742
 
 
743
 
    def test_cannot_bind_address(self):
744
 
        # see <https://bugs.launchpad.net/bzr/+bug/286871>
745
 
        e = errors.CannotBindAddress('example.com', 22,
746
 
            socket.error(13, 'Permission denied'))
747
 
        self.assertContainsRe(str(e),
748
 
            r'Cannot bind address "example\.com:22":.*Permission denied')
749
 
 
750
 
    def test_file_timestamp_unavailable(self):            
751
 
        e = errors.FileTimestampUnavailable("/path/foo")
752
 
        self.assertEquals("The filestamp for /path/foo is not available.",
753
 
            str(e))
754
 
            
755
 
    def test_transform_rename_failed(self):
756
 
        e = errors.TransformRenameFailed(u"from", u"to", "readonly file", 2)
757
 
        self.assertEquals(
758
 
            u"Failed to rename from to to: readonly file",
759
 
            str(e))