Merge lp:~gz/brz/py3_errors into lp:brz

Proposed by Martin Packman
Status: Merged
Approved by: Martin Packman
Approved revision: no longer in the source branch.
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~gz/brz/py3_errors
Merge into: lp:brz
Diff against target: 593 lines (+158/-154)
4 files modified
breezy/errors.py (+41/-47)
breezy/inventory_delta.py (+34/-20)
breezy/tests/test_errors.py (+78/-84)
breezy/tests/test_inventory_delta.py (+5/-3)
To merge this branch: bzr merge lp:~gz/brz/py3_errors
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+325347@code.launchpad.net

Commit message

Improve Python 3 compatibility of errors module

Description of the change

Selection of changes that move breezy.errors towards Python 3 compatibility.

Implement __hash__ which is now required on classes that have an __eq__.

Only provide __str__/__unicode__ methods as appropriate.

In the tests, moved out errors that actually require a transport to a separate class, and fix a few other suspect assertions.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :

Running landing tests failed
http://10.242.247.184:8080/job/brz-dev/97/

Revision history for this message
Martin Packman (gz) wrote :

Ended up doing rather more changed than intended to inventory_delta based on the one test failure there, so this branch could probably do with another quick review.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/errors.py'
2--- breezy/errors.py 2017-05-22 00:56:52 +0000
3+++ breezy/errors.py 2017-06-09 16:32:43 +0000
4@@ -19,6 +19,10 @@
5
6 from __future__ import absolute_import
7
8+from .sixish import (
9+ PY3,
10+ )
11+
12 # TODO: is there any value in providing the .args field used by standard
13 # python exceptions? A list of values with no names seems less useful
14 # to me.
15@@ -87,6 +91,7 @@
16 if s is not None:
17 # contains a preformatted message
18 return s
19+ err = None
20 try:
21 fmt = self._get_format_string()
22 if fmt:
23@@ -96,34 +101,20 @@
24 # never a 'unicode' object.
25 return s
26 except Exception as e:
27- pass # just bind to 'e' for formatting below
28- else:
29- e = None
30+ err = e
31 return 'Unprintable exception %s: dict=%r, fmt=%r, error=%r' \
32 % (self.__class__.__name__,
33 self.__dict__,
34 getattr(self, '_fmt', None),
35- e)
36-
37- def __unicode__(self):
38- u = self._format()
39- if isinstance(u, str):
40- # Try decoding the str using the default encoding.
41- u = unicode(u)
42- elif not isinstance(u, unicode):
43- # Try to make a unicode object from it, because __unicode__ must
44- # return a unicode object.
45- u = unicode(u)
46- return u
47-
48- def __str__(self):
49- s = self._format()
50- if isinstance(s, unicode):
51- s = s.encode('utf8')
52- else:
53- # __str__ must return a str.
54- s = str(s)
55- return s
56+ err)
57+
58+ if PY3:
59+ __str__ = _format
60+ else:
61+ def __str__(self):
62+ return self._format().encode('utf-8')
63+
64+ __unicode__ = _format
65
66 def __repr__(self):
67 return '%s(%s)' % (self.__class__.__name__, str(self))
68@@ -133,13 +124,16 @@
69 fmt = getattr(self, '_fmt', None)
70 if fmt is not None:
71 from breezy.i18n import gettext
72- return gettext(unicode(fmt)) # _fmt strings should be ascii
73+ return gettext(fmt) # _fmt strings should be ascii
74
75 def __eq__(self, other):
76 if self.__class__ is not other.__class__:
77 return NotImplemented
78 return self.__dict__ == other.__dict__
79
80+ def __hash__(self):
81+ return id(self)
82+
83
84 class InternalBzrError(BzrError):
85 """Base class for errors that are internal in nature.
86@@ -665,30 +659,30 @@
87 def __repr__(self):
88 return '<%s %r>' % (self.__class__.__name__, self.__dict__)
89
90- def _format(self):
91- # XXX: Ideally self.detail would be a property, but Exceptions in
92- # Python 2.4 have to be old-style classes so properties don't work.
93- # Instead we override _format.
94+ def _get_format_string(self):
95+ # GZ 2017-06-08: Not the best place to lazy fill detail in.
96 if self.detail is None:
97- if self.bzrdir is not None:
98- try:
99- self.bzrdir.open_repository()
100- except NoRepositoryPresent:
101- self.detail = ''
102- except Exception:
103- # Just ignore unexpected errors. Raising arbitrary errors
104- # during str(err) can provoke strange bugs. Concretely
105- # Launchpad's codehosting managed to raise NotBranchError
106- # here, and then get stuck in an infinite loop/recursion
107- # trying to str() that error. All this error really cares
108- # about that there's no working repository there, and if
109- # open_repository() fails, there probably isn't.
110- self.detail = ''
111- else:
112- self.detail = ': location is a repository'
113+ self.detail = self._get_detail()
114+ return super(NotBranchError, self)._get_format_string()
115+
116+ def _get_detail(self):
117+ if self.bzrdir is not None:
118+ try:
119+ self.bzrdir.open_repository()
120+ except NoRepositoryPresent:
121+ return ''
122+ except Exception as e:
123+ # Just ignore unexpected errors. Raising arbitrary errors
124+ # during str(err) can provoke strange bugs. Concretely
125+ # Launchpad's codehosting managed to raise NotBranchError
126+ # here, and then get stuck in an infinite loop/recursion
127+ # trying to str() that error. All this error really cares
128+ # about that there's no working repository there, and if
129+ # open_repository() fails, there probably isn't.
130+ return ': ' + e.__class__.__name__
131 else:
132- self.detail = ''
133- return PathError._format(self)
134+ return ': location is a repository'
135+ return ''
136
137
138 class NoSubmitBranch(PathError):
139
140=== modified file 'breezy/inventory_delta.py'
141--- breezy/inventory_delta.py 2017-05-25 01:35:55 +0000
142+++ breezy/inventory_delta.py 2017-06-09 16:32:43 +0000
143@@ -36,12 +36,17 @@
144
145 class InventoryDeltaError(errors.BzrError):
146 """An error when serializing or deserializing an inventory delta."""
147-
148+
149 # Most errors when serializing and deserializing are due to bugs, although
150 # damaged input (i.e. a bug in a different process) could cause
151 # deserialization errors too.
152 internal_error = True
153
154+ def __init__(self, format_string, **kwargs):
155+ # Let each error supply a custom format string and arguments.
156+ self._fmt = format_string
157+ super(InventoryDeltaError, self).__init__(**kwargs)
158+
159
160 class IncompatibleInventoryDelta(errors.BzrError):
161 """The delta could not be deserialised because its contents conflict with
162@@ -70,7 +75,8 @@
163 exec_bytes = ''
164 size_exec_sha = (entry.text_size, exec_bytes, entry.text_sha1)
165 if None in size_exec_sha:
166- raise InventoryDeltaError('Missing size or sha for %s' % entry.file_id)
167+ raise InventoryDeltaError(
168+ 'Missing size or sha for %(fileid)r', fileid=entry.file_id)
169 return "file\x00%d\x00%s\x00%s" % size_exec_sha
170
171
172@@ -81,7 +87,8 @@
173 """
174 target = entry.symlink_target
175 if target is None:
176- raise InventoryDeltaError('Missing target for %s' % entry.file_id)
177+ raise InventoryDeltaError(
178+ 'Missing target for %(fileid)r', fileid=entry.file_id)
179 return "link\x00%s" % target.encode('utf8')
180
181
182@@ -93,7 +100,7 @@
183 tree_revision = entry.reference_revision
184 if tree_revision is None:
185 raise InventoryDeltaError(
186- 'Missing reference revision for %s' % entry.file_id)
187+ 'Missing reference revision for %(fileid)r', fileid=entry.file_id)
188 return "tree\x00%s" % tree_revision
189
190
191@@ -180,9 +187,10 @@
192 to_line = self._delta_item_to_line
193 for delta_item in delta_to_new:
194 line = to_line(delta_item, new_name)
195- if line.__class__ != str:
196+ # GZ 2017-06-09: Not really worth asserting this here
197+ if line.__class__ != bytes:
198 raise InventoryDeltaError(
199- 'to_line generated non-str output %r' % lines[-1])
200+ 'to_line gave non-bytes output %(line)r', line=lines[-1])
201 lines.append(line)
202 lines.sort()
203 lines[0] = "format: %s\n" % FORMAT_1
204@@ -236,10 +244,12 @@
205 # xml5 serializer.
206 if last_modified != new_version:
207 raise InventoryDeltaError(
208- 'Version present for / in %s (%s != %s)'
209- % (file_id, last_modified, new_version))
210+ 'Version present for / in %(fileid)r'
211+ ' (%(last)r != %(new)r)',
212+ fileid=file_id, last=last_modified, new=new_version)
213 if last_modified is None:
214- raise InventoryDeltaError("no version for fileid %s" % file_id)
215+ raise InventoryDeltaError(
216+ "no version for fileid %(fileid)r", fileid=file_id)
217 content = self._entry_to_content[entry.kind](entry)
218 return ("%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %
219 (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
220@@ -265,7 +275,7 @@
221 elif value == "false":
222 return False
223 else:
224- raise InventoryDeltaError("value %r is not a bool" % (value,))
225+ raise InventoryDeltaError("value %(val)r is not a bool", val=value)
226
227 def parse_text_bytes(self, bytes):
228 """Parse the text bytes of a serialized inventory delta.
229@@ -281,10 +291,12 @@
230 """
231 if bytes[-1:] != '\n':
232 last_line = bytes.rsplit('\n', 1)[-1]
233- raise InventoryDeltaError('last line not empty: %r' % (last_line,))
234+ raise InventoryDeltaError(
235+ 'last line not empty: %(line)r', line=last_line)
236 lines = bytes.split('\n')[:-1] # discard the last empty line
237 if not lines or lines[0] != 'format: %s' % FORMAT_1:
238- raise InventoryDeltaError('unknown format %r' % lines[0:1])
239+ raise InventoryDeltaError(
240+ 'unknown format %(line)r', line=lines[0:1])
241 if len(lines) < 2 or not lines[1].startswith('parent: '):
242 raise InventoryDeltaError('missing parent: marker')
243 delta_parent_id = lines[1][8:]
244@@ -310,22 +322,24 @@
245 parent_id = parent_id or None
246 if file_id in seen_ids:
247 raise InventoryDeltaError(
248- "duplicate file id in inventory delta %r" % lines)
249+ "duplicate file id %(fileid)r", fileid=file_id)
250 seen_ids.add(file_id)
251 if (newpath_utf8 == '/' and not delta_versioned_root and
252 last_modified != delta_version_id):
253 # Delta claims to be not have a versioned root, yet here's
254 # a root entry with a non-default version.
255- raise InventoryDeltaError("Versioned root found: %r" % line)
256+ raise InventoryDeltaError(
257+ "Versioned root found: %(line)r", line=line)
258 elif newpath_utf8 != 'None' and last_modified[-1] == ':':
259 # Deletes have a last_modified of null:, but otherwise special
260 # revision ids should not occur.
261- raise InventoryDeltaError('special revisionid found: %r' % line)
262+ raise InventoryDeltaError(
263+ 'special revisionid found: %(line)r', line=line)
264 if content.startswith('tree\x00'):
265 if delta_tree_references is False:
266 raise InventoryDeltaError(
267 "Tree reference found (but header said "
268- "tree_references: false): %r" % line)
269+ "tree_references: false): %(line)r", line=line)
270 elif not self._allow_tree_references:
271 raise IncompatibleInventoryDelta(
272 "Tree reference not allowed")
273@@ -333,8 +347,8 @@
274 oldpath = None
275 elif oldpath_utf8[:1] != '/':
276 raise InventoryDeltaError(
277- "oldpath invalid (does not start with /): %r"
278- % (oldpath_utf8,))
279+ "oldpath invalid (does not start with /): %(path)r",
280+ path=oldpath_utf8)
281 else:
282 oldpath_utf8 = oldpath_utf8[1:]
283 oldpath = oldpath_utf8.decode('utf8')
284@@ -342,8 +356,8 @@
285 newpath = None
286 elif newpath_utf8[:1] != '/':
287 raise InventoryDeltaError(
288- "newpath invalid (does not start with /): %r"
289- % (newpath_utf8,))
290+ "newpath invalid (does not start with /): %(path)r",
291+ path=newpath_utf8)
292 else:
293 # Trim leading slash
294 newpath_utf8 = newpath_utf8[1:]
295
296=== modified file 'breezy/tests/test_errors.py'
297--- breezy/tests/test_errors.py 2017-05-22 00:56:52 +0000
298+++ breezy/tests/test_errors.py 2017-06-09 16:32:43 +0000
299@@ -25,16 +25,15 @@
300 controldir,
301 errors,
302 osutils,
303+ tests,
304 urlutils,
305 )
306-from . import (
307- TestCase,
308- TestCaseWithTransport,
309- TestSkipped,
310+from ..sixish import (
311+ text_type,
312 )
313
314
315-class TestErrors(TestCaseWithTransport):
316+class TestErrors(tests.TestCase):
317
318 def test_no_arg_named_message(self):
319 """Ensure the __init__ and _fmt in errors do not have "message" arg.
320@@ -45,10 +44,6 @@
321 See bug #603461
322 """
323 fmt_pattern = re.compile("%\(message\)[sir]")
324- subclasses_present = getattr(errors.BzrError, '__subclasses__', None)
325- if not subclasses_present:
326- raise TestSkipped('__subclasses__ attribute required for classes. '
327- 'Requires Python 2.5 or later.')
328 for c in errors.BzrError.__subclasses__():
329 init = getattr(c, '__init__', None)
330 fmt = getattr(c, '_fmt', None)
331@@ -62,11 +57,11 @@
332 '"errors.%s._fmt"' % c.__name__))
333
334 def test_bad_filename_encoding(self):
335- error = errors.BadFilenameEncoding('bad/filen\xe5me', 'UTF-8')
336- self.assertEqualDiff(
337- "Filename 'bad/filen\\xe5me' is not valid in your current"
338- " filesystem encoding UTF-8",
339- str(error))
340+ error = errors.BadFilenameEncoding(b'bad/filen\xe5me', 'UTF-8')
341+ self.assertContainsRe(
342+ str(error),
343+ "^Filename b?'bad/filen\\\\xe5me' is not valid in your current"
344+ " filesystem encoding UTF-8$")
345
346 def test_corrupt_dirstate(self):
347 error = errors.CorruptDirstate('path/to/dirstate', 'the reason why')
348@@ -199,19 +194,6 @@
349 self.assertEqualDiff(
350 "The medium 'a medium' is not connected.", str(error))
351
352- def test_no_public_branch(self):
353- b = self.make_branch('.')
354- error = errors.NoPublicBranch(b)
355- url = urlutils.unescape_for_display(b.base, 'ascii')
356- self.assertEqualDiff(
357- 'There is no public branch set for "%s".' % url, str(error))
358-
359- def test_no_repo(self):
360- dir = controldir.ControlDir.create(self.get_url())
361- error = errors.NoRepositoryPresent(dir)
362- self.assertNotEqual(-1, str(error).find((dir.transport.clone('..').base)))
363- self.assertEqual(-1, str(error).find((dir.transport.base)))
364-
365 def test_no_smart_medium(self):
366 error = errors.NoSmartMedium("a transport")
367 self.assertEqualDiff("The transport 'a transport' cannot tunnel the "
368@@ -305,19 +287,11 @@
369 "The branch format someformat is already at the most "
370 "recent format.", str(error))
371
372- def test_corrupt_repository(self):
373- repo = self.make_repository('.')
374- error = errors.CorruptRepository(repo)
375- self.assertEqualDiff("An error has been detected in the repository %s.\n"
376- "Please run brz reconcile on this repository." %
377- repo.bzrdir.root_transport.base,
378- str(error))
379-
380 def test_read_error(self):
381 # a unicode path to check that %r is being used.
382 path = u'a path'
383 error = errors.ReadError(path)
384- self.assertEqualDiff("Error reading from u'a path'.", str(error))
385+ self.assertContainsRe(str(error), "^Error reading from u?'a path'.$")
386
387 def test_bad_index_format_signature(self):
388 error = errors.BadIndexFormatSignature("foo", "bar")
389@@ -445,14 +419,6 @@
390 'See "brz help bugs" for more information on this feature.',
391 str(error))
392
393- def test_unknown_bug_tracker_abbreviation(self):
394- """Test the formatting of UnknownBugTrackerAbbreviation."""
395- branch = self.make_branch('some_branch')
396- error = errors.UnknownBugTrackerAbbreviation('xxx', branch)
397- self.assertEqual(
398- "Cannot find registered bug tracker called xxx on %s" % branch,
399- str(error))
400-
401 def test_unexpected_smart_server_response(self):
402 e = errors.UnexpectedSmartServerResponse(('not yes',))
403 self.assertEqual(
404@@ -495,14 +461,12 @@
405
406 def test_duplicate_record_name_error(self):
407 """Test the formatting of DuplicateRecordNameError."""
408- e = errors.DuplicateRecordNameError(u"n\xe5me".encode('utf-8'))
409+ e = errors.DuplicateRecordNameError(b"n\xc3\xa5me")
410 self.assertEqual(
411- "Container has multiple records with the same name: n\xc3\xa5me",
412- str(e))
413+ u"Container has multiple records with the same name: n\xe5me",
414+ text_type(e))
415
416 def test_check_error(self):
417- # This has a member called 'message', which is problematic in
418- # python2.5 because that is a slot on the base Exception class
419 e = errors.BzrCheckError('example check failure')
420 self.assertEqual(
421 "Internal check failed: example check failure",
422@@ -534,7 +498,7 @@
423 str(err))
424 err = errors.UnableCreateSymlink(path=u'\xb5')
425 self.assertEqual(
426- "Unable to create symlink u'\\xb5' on this platform",
427+ "Unable to create symlink %s on this platform" % repr(u'\xb5'),
428 str(err))
429
430 def test_invalid_url_join(self):
431@@ -569,10 +533,7 @@
432 err = errors.TipChangeRejected(u'Unicode message\N{INTERROBANG}')
433 self.assertEqual(
434 u'Tip change rejected: Unicode message\N{INTERROBANG}',
435- unicode(err))
436- self.assertEqual(
437- 'Tip change rejected: Unicode message\xe2\x80\xbd',
438- str(err))
439+ text_type(err))
440
441 def test_error_from_smart_server(self):
442 error_tuple = ('error', 'tuple')
443@@ -647,17 +608,6 @@
444 err = errors.NotBranchError('path')
445 self.assertEqual('Not a branch: "path".', str(err))
446
447- def test_not_branch_bzrdir_with_repo(self):
448- bzrdir = self.make_repository('repo').bzrdir
449- err = errors.NotBranchError('path', bzrdir=bzrdir)
450- self.assertEqual(
451- 'Not a branch: "path": location is a repository.', str(err))
452-
453- def test_not_branch_bzrdir_without_repo(self):
454- bzrdir = self.make_bzrdir('bzrdir')
455- err = errors.NotBranchError('path', bzrdir=bzrdir)
456- self.assertEqual('Not a branch: "path".', str(err))
457-
458 def test_not_branch_bzrdir_with_recursive_not_branch_error(self):
459 class FakeBzrDir(object):
460 def open_repository(self):
461@@ -665,24 +615,7 @@
462 # which in turn will another, identical NotBranchError.
463 raise errors.NotBranchError('path', bzrdir=FakeBzrDir())
464 err = errors.NotBranchError('path', bzrdir=FakeBzrDir())
465- self.assertEqual('Not a branch: "path".', str(err))
466-
467- def test_not_branch_laziness(self):
468- real_bzrdir = self.make_bzrdir('path')
469- class FakeBzrDir(object):
470- def __init__(self):
471- self.calls = []
472- def open_repository(self):
473- self.calls.append('open_repository')
474- raise errors.NoRepositoryPresent(real_bzrdir)
475- fake_bzrdir = FakeBzrDir()
476- err = errors.NotBranchError('path', bzrdir=fake_bzrdir)
477- self.assertEqual([], fake_bzrdir.calls)
478- str(err)
479- self.assertEqual(['open_repository'], fake_bzrdir.calls)
480- # Stringifying twice doesn't try to open a repository twice.
481- str(err)
482- self.assertEqual(['open_repository'], fake_bzrdir.calls)
483+ self.assertEqual('Not a branch: "path": NotBranchError.', str(err))
484
485 def test_invalid_pattern(self):
486 error = errors.InvalidPattern('Bad pattern msg.')
487@@ -721,7 +654,7 @@
488 __doc__ = """This class has a docstring but no format string."""
489
490
491-class TestErrorFormatting(TestCase):
492+class TestErrorFormatting(tests.TestCase):
493
494 def test_always_str(self):
495 e = PassThroughError(u'\xb5', 'bar')
496@@ -764,3 +697,64 @@
497 self.assertEqual(
498 u"Failed to rename from to to: readonly file",
499 str(e))
500+
501+
502+class TestErrorsUsingTransport(tests.TestCaseWithMemoryTransport):
503+ """Tests for errors that need to use a branch or repo."""
504+
505+ def test_no_public_branch(self):
506+ b = self.make_branch('.')
507+ error = errors.NoPublicBranch(b)
508+ url = urlutils.unescape_for_display(b.base, 'ascii')
509+ self.assertEqualDiff(
510+ 'There is no public branch set for "%s".' % url, str(error))
511+
512+ def test_no_repo(self):
513+ dir = controldir.ControlDir.create(self.get_url())
514+ error = errors.NoRepositoryPresent(dir)
515+ self.assertNotEqual(-1, str(error).find((dir.transport.clone('..').base)))
516+ self.assertEqual(-1, str(error).find((dir.transport.base)))
517+
518+ def test_corrupt_repository(self):
519+ repo = self.make_repository('.')
520+ error = errors.CorruptRepository(repo)
521+ self.assertEqualDiff("An error has been detected in the repository %s.\n"
522+ "Please run brz reconcile on this repository." %
523+ repo.bzrdir.root_transport.base,
524+ str(error))
525+
526+ def test_unknown_bug_tracker_abbreviation(self):
527+ """Test the formatting of UnknownBugTrackerAbbreviation."""
528+ branch = self.make_branch('some_branch')
529+ error = errors.UnknownBugTrackerAbbreviation('xxx', branch)
530+ self.assertEqual(
531+ "Cannot find registered bug tracker called xxx on %s" % branch,
532+ str(error))
533+
534+ def test_not_branch_bzrdir_with_repo(self):
535+ bzrdir = self.make_repository('repo').bzrdir
536+ err = errors.NotBranchError('path', bzrdir=bzrdir)
537+ self.assertEqual(
538+ 'Not a branch: "path": location is a repository.', str(err))
539+
540+ def test_not_branch_bzrdir_without_repo(self):
541+ bzrdir = self.make_bzrdir('bzrdir')
542+ err = errors.NotBranchError('path', bzrdir=bzrdir)
543+ self.assertEqual('Not a branch: "path".', str(err))
544+
545+ def test_not_branch_laziness(self):
546+ real_bzrdir = self.make_bzrdir('path')
547+ class FakeBzrDir(object):
548+ def __init__(self):
549+ self.calls = []
550+ def open_repository(self):
551+ self.calls.append('open_repository')
552+ raise errors.NoRepositoryPresent(real_bzrdir)
553+ fake_bzrdir = FakeBzrDir()
554+ err = errors.NotBranchError('path', bzrdir=fake_bzrdir)
555+ self.assertEqual([], fake_bzrdir.calls)
556+ str(err)
557+ self.assertEqual(['open_repository'], fake_bzrdir.calls)
558+ # Stringifying twice doesn't try to open a repository twice.
559+ str(err)
560+ self.assertEqual(['open_repository'], fake_bzrdir.calls)
561
562=== modified file 'breezy/tests/test_inventory_delta.py'
563--- breezy/tests/test_inventory_delta.py 2017-05-22 00:56:52 +0000
564+++ breezy/tests/test_inventory_delta.py 2017-06-09 16:32:43 +0000
565@@ -354,7 +354,7 @@
566 versioned_root=True, tree_references=True)
567 err = self.assertRaises(InventoryDeltaError,
568 serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
569- self.assertEqual(str(err), 'no version for fileid id')
570+ self.assertContainsRe(str(err), "^no version for fileid b?'id'$")
571
572 def test_richroot_unversioned_root_errors(self):
573 old_inv = Inventory(None)
574@@ -366,7 +366,8 @@
575 versioned_root=True, tree_references=True)
576 err = self.assertRaises(InventoryDeltaError,
577 serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
578- self.assertEqual(str(err), 'no version for fileid TREE_ROOT')
579+ self.assertContainsRe(
580+ str(err), "no version for fileid b?'TREE_ROOT'$")
581
582 def test_nonrichroot_versioned_root_errors(self):
583 old_inv = Inventory(None)
584@@ -379,7 +380,8 @@
585 versioned_root=False, tree_references=True)
586 err = self.assertRaises(InventoryDeltaError,
587 serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
588- self.assertStartsWith(str(err), 'Version present for / in TREE_ROOT')
589+ self.assertContainsRe(
590+ str(err), "^Version present for / in b?'TREE_ROOT'")
591
592 def test_unknown_kind_errors(self):
593 old_inv = Inventory(None)

Subscribers

People subscribed via source and target branches