Description: Consistent output for XML serialization.
Origin: commit, revision id: jelmer@jelmer.uk-20200131025640-njh60c73nvr551x8
Author: Jelmer Vernooĳ <jelmer@jelmer.uk>
Last-Update: 2020-01-31
Applied-Upstream: no
X-Bzr-Revision-Id: jelmer@jelmer.uk-20200131025640-njh60c73nvr551x8

=== modified file 'breezy/bzr/chk_serializer.py'
--- old/breezy/bzr/chk_serializer.py	2018-11-11 04:08:32 +0000
+++ new/breezy/bzr/chk_serializer.py	2020-01-31 02:55:46 +0000
@@ -101,8 +101,8 @@
         ])
         return bencode.bencode(ret)
 
-    def write_revision(self, rev, f):
-        f.write(self.write_revision_to_string(rev))
+    def write_revision_to_lines(self, rev):
+        return self.write_revision_to_string(rev).splitlines(True)
 
     def read_revision_from_string(self, text):
         # TODO: consider writing a Revision decoder, rather than using the
@@ -223,14 +223,14 @@
         output = []
         append = output.append
         if inv.revision_id is not None:
-            revid1 = b' revision_id="'
-            revid2 = xml_serializer.encode_and_escape(inv.revision_id)
+            revid = b''.join(
+                [b' revision_id="',
+                 xml_serializer.encode_and_escape(inv.revision_id), b'"'])
         else:
-            revid1 = b""
-            revid2 = b""
-        append(b'<inventory format="%s"%s%s>\n' % (
-            self.format_num, revid1, revid2))
-        append(b'<directory file_id="%s name="%s revision="%s />\n' % (
+            revid = b""
+        append(b'<inventory format="%s"%s>\n' % (
+            self.format_num, revid))
+        append(b'<directory file_id="%s" name="%s" revision="%s" />\n' % (
             xml_serializer.encode_and_escape(inv.root.file_id),
             xml_serializer.encode_and_escape(inv.root.name),
             xml_serializer.encode_and_escape(inv.root.revision)))

=== modified file 'breezy/bzr/remote.py'
--- old/breezy/bzr/remote.py	2019-06-16 19:12:23 +0000
+++ new/breezy/bzr/remote.py	2020-01-31 02:51:24 +0000
@@ -2002,11 +2002,11 @@
     def _add_revision(self, rev):
         if self._real_repository is not None:
             return self._real_repository._add_revision(rev)
-        text = self._serializer.write_revision_to_string(rev)
+        lines = self._serializer.write_revision_to_lines(rev)
         key = (rev.revision_id,)
         parents = tuple((parent,) for parent in rev.parent_ids)
         self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
-            [('revisions', [FulltextContentFactory(key, parents, None, text)])],
+            [('revisions', [ChunkedContentFactory(key, parents, None, lines, chunks_are_lines=True)])],
             self._format, self._write_group_tokens)
 
     def get_inventory(self, revision_id):

=== modified file 'breezy/bzr/serializer.py'
--- old/breezy/bzr/serializer.py	2018-11-11 04:08:32 +0000
+++ new/breezy/bzr/serializer.py	2020-01-31 02:55:48 +0000
@@ -75,12 +75,12 @@
         """See read_inventory_from_string."""
         raise NotImplementedError(self.read_inventory)
 
-    def write_revision(self, rev, f):
-        raise NotImplementedError(self.write_revision)
-
     def write_revision_to_string(self, rev):
         raise NotImplementedError(self.write_revision_to_string)
 
+    def write_revision_to_lines(self, rev):
+        raise NotImplementedError(self.write_revision_to_lines)
+
     def read_revision(self, f):
         raise NotImplementedError(self.read_revision)
 

=== modified file 'breezy/bzr/vf_repository.py'
--- old/breezy/bzr/vf_repository.py	2019-02-04 19:39:30 +0000
+++ new/breezy/bzr/vf_repository.py	2020-01-31 02:52:02 +0000
@@ -768,10 +768,10 @@
         self._add_revision(rev)
 
     def _add_revision(self, revision):
-        text = self._serializer.write_revision_to_string(revision)
+        lines = self._serializer.write_revision_to_lines(revision)
         key = (revision.revision_id,)
         parents = tuple((parent,) for parent in revision.parent_ids)
-        self.revisions.add_lines(key, parents, osutils.split_lines(text))
+        self.revisions.add_lines(key, parents, lines)
 
     def _check_inventories(self, checker):
         """Check the inventories found from the revision scan.

=== modified file 'breezy/bzr/xml5.py'
--- old/breezy/bzr/xml5.py	2018-11-11 15:40:12 +0000
+++ new/breezy/bzr/xml5.py	2020-01-31 02:45:16 +0000
@@ -101,19 +101,14 @@
     def _append_inventory_root(self, append, inv):
         """Append the inventory root to output."""
         if inv.root.file_id not in (None, inventory.ROOT_ID):
-            fileid1 = b' file_id="'
-            fileid2 = encode_and_escape(inv.root.file_id)
+            fileid = b''.join([b' file_id="', encode_and_escape(inv.root.file_id), b'"'])
         else:
-            fileid1 = b""
-            fileid2 = b""
+            fileid = b""
         if inv.revision_id is not None:
-            revid1 = b' revision_id="'
-            revid2 = encode_and_escape(inv.revision_id)
+            revid = b''.join([b' revision_id="', encode_and_escape(inv.revision_id), b'"'])
         else:
-            revid1 = b""
-            revid2 = b""
-        append(b'<inventory%s%s format="5"%s%s>\n' % (
-            fileid1, fileid2, revid1, revid2))
+            revid = b""
+        append(b'<inventory%s format="5"%s>\n' % (fileid, revid))
 
 
 serializer_v5 = Serializer_v5()

=== modified file 'breezy/bzr/xml8.py'
--- old/breezy/bzr/xml8.py	2018-11-16 11:37:47 +0000
+++ new/breezy/bzr/xml8.py	2020-01-31 02:49:18 +0000
@@ -169,61 +169,58 @@
     def _append_inventory_root(self, append, inv):
         """Append the inventory root to output."""
         if inv.revision_id is not None:
-            revid1 = b' revision_id="'
-            revid2 = encode_and_escape(inv.revision_id)
+            revid1 = b''.join(
+                [b' revision_id="', encode_and_escape(inv.revision_id), b'"'])
         else:
             revid1 = b""
-            revid2 = b""
-        append(b'<inventory format="%s"%s%s>\n' % (
-            self.format_num, revid1, revid2))
-        append(b'<directory file_id="%s name="%s revision="%s />\n' % (
+        append(b'<inventory format="%s"%s>\n' % (
+            self.format_num, revid1))
+        append(b'<directory file_id="%s" name="%s" revision="%s" />\n' % (
             encode_and_escape(inv.root.file_id),
             encode_and_escape(inv.root.name),
             encode_and_escape(inv.root.revision)))
 
-    def _pack_revision(self, rev):
+    def write_revision_to_lines(self, rev):
         """Revision object -> xml tree"""
         # For the XML format, we need to write them as Unicode rather than as
         # utf-8 strings. So that cElementTree can handle properly escaping
         # them.
-        decode_utf8 = cache_utf8.decode
-        revision_id = rev.revision_id
-        format_num = self.format_num
-        if self.revision_format_num is not None:
-            format_num = self.revision_format_num
-        root = Element('revision',
-                       committer=rev.committer,
-                       timestamp='%.3f' % rev.timestamp,
-                       revision_id=decode_utf8(revision_id),
-                       inventory_sha1=rev.inventory_sha1.decode('ascii'),
-                       format=format_num.decode(),
-                       )
+        lines = []
+        el = (b'<revision committer="%s" format="%s" '
+              b'inventory_sha1="%s" revision_id="%s" '
+              b'timestamp="%.3f"' % (
+                  encode_and_escape(rev.committer),
+                  self.revision_format_num or self.format_num,
+                  rev.inventory_sha1,
+                  encode_and_escape(cache_utf8.decode(rev.revision_id)),
+                  rev.timestamp))
         if rev.timezone is not None:
-            root.set('timezone', str(rev.timezone))
-        root.text = '\n'
-        msg = SubElement(root, 'message')
-        msg.text = escape_invalid_chars(rev.message)[0]
-        msg.tail = '\n'
+            el += b' timezone="%s"' % str(rev.timezone).encode('ascii')
+        lines.append(el + b'>\n')
+        message = encode_and_escape(escape_invalid_chars(rev.message)[0])
+        lines.extend((b'<message>' + message + b'</message>\n').splitlines(True))
         if rev.parent_ids:
-            pelts = SubElement(root, 'parents')
-            pelts.tail = pelts.text = '\n'
+            lines.append(b'<parents>\n')
             for parent_id in rev.parent_ids:
                 _mod_revision.check_not_reserved_id(parent_id)
-                p = SubElement(pelts, 'revision_ref')
-                p.tail = '\n'
-                p.set('revision_id', decode_utf8(parent_id))
+                lines.append(
+                    b'<revision_ref revision_id="%s" />\n'
+                    % encode_and_escape(cache_utf8.decode(parent_id)))
+            lines.append(b'</parents>\n')
         if rev.properties:
-            self._pack_revision_properties(rev, root)
-        return root
-
-    def _pack_revision_properties(self, rev, under_element):
-        top_elt = SubElement(under_element, 'properties')
-        for prop_name, prop_value in sorted(rev.properties.items()):
-            prop_elt = SubElement(top_elt, 'property')
-            prop_elt.set('name', prop_name)
-            prop_elt.text = prop_value
-            prop_elt.tail = '\n'
-        top_elt.tail = '\n'
+            preamble = b'<properties>'
+            for prop_name, prop_value in sorted(rev.properties.items()):
+                if prop_value:
+                    proplines = (preamble + b'<property name="%s">%s</property>\n' % (
+                        encode_and_escape(prop_name),
+                        encode_and_escape(escape_invalid_chars(prop_value)[0]))).splitlines(True)
+                else:
+                    proplines = [preamble + b'<property name="%s" />\n' % (encode_and_escape(prop_name), )]
+                preamble = b''
+                lines.extend(proplines)
+            lines.append(b'</properties>\n')
+        lines.append(b'</revision>\n')
+        return lines
 
     def _unpack_entry(self, elt, entry_cache=None, return_from_cache=False):
         # This is here because it's overridden by xml7

=== modified file 'breezy/bzr/xml_serializer.py'
--- old/breezy/bzr/xml_serializer.py	2019-02-14 05:21:21 +0000
+++ new/breezy/bzr/xml_serializer.py	2020-01-31 02:55:50 +0000
@@ -96,11 +96,8 @@
         except ParseError as e:
             raise errors.UnexpectedInventoryFormat(str(e))
 
-    def write_revision(self, rev, f):
-        self._write_element(self._pack_revision(rev), f)
-
     def write_revision_to_string(self, rev):
-        return tostring(self._pack_revision(rev)) + b'\n'
+        return b''.join(self.write_revision_to_lines(rev))
 
     def read_revision(self, f):
         return self._unpack_revision(self._read_element(f))
@@ -108,10 +105,6 @@
     def read_revision_from_string(self, xml_string):
         return self._unpack_revision(fromstring(xml_string))
 
-    def _write_element(self, elt, f):
-        ElementTree(elt).write(f, 'utf-8')
-        f.write(b'\n')
-
     def _read_element(self, f):
         return ElementTree().parse(f)
 
@@ -220,12 +213,12 @@
             # better than entity escapes, but cElementTree seems to do just
             # fine either way)
             text = _unicode_re.sub(
-                _unicode_escape_replace, unicode_or_utf8_str).encode() + b'"'
+                _unicode_escape_replace, unicode_or_utf8_str).encode()
         else:
             # Plain strings are considered to already be in utf-8 so we do a
             # slightly different method for escaping.
             text = _utf8_re.sub(_utf8_escape_replace,
-                                unicode_or_utf8_str) + b'"'
+                                unicode_or_utf8_str)
         _map[unicode_or_utf8_str] = text
     return text
 
@@ -378,70 +371,69 @@
     root_path, root_ie = next(entries)
     for path, ie in entries:
         if ie.parent_id != root_id:
-            parent_str = b' parent_id="'
-            parent_id = encode_and_escape(ie.parent_id)
+            parent_str = b''.join(
+                [b' parent_id="', encode_and_escape(ie.parent_id), b'"'])
         else:
             parent_str = b''
-            parent_id = b''
         if ie.kind == 'file':
             if ie.executable:
                 executable = b' executable="yes"'
             else:
                 executable = b''
             if not working:
-                append(b'<file%s file_id="%s name="%s%s%s revision="%s '
+                append(b'<file%s file_id="%s" name="%s"%s revision="%s" '
                        b'text_sha1="%s" text_size="%d" />\n' % (
                            executable, encode_and_escape(ie.file_id),
-                           encode_and_escape(ie.name), parent_str, parent_id,
+                           encode_and_escape(ie.name), parent_str,
                            encode_and_escape(ie.revision), ie.text_sha1,
                            ie.text_size))
             else:
-                append(b'<file%s file_id="%s name="%s%s%s />\n' % (
+                append(b'<file%s file_id="%s" name="%s"%s />\n' % (
                     executable, encode_and_escape(ie.file_id),
-                    encode_and_escape(ie.name), parent_str, parent_id))
+                    encode_and_escape(ie.name), parent_str))
         elif ie.kind == 'directory':
             if not working:
-                append(b'<directory file_id="%s name="%s%s%s revision="%s '
+                append(b'<directory file_id="%s" name="%s"%s revision="%s" '
                        b'/>\n' % (
                            encode_and_escape(ie.file_id),
                            encode_and_escape(ie.name),
-                           parent_str, parent_id,
+                           parent_str,
                            encode_and_escape(ie.revision)))
             else:
-                append(b'<directory file_id="%s name="%s%s%s />\n' % (
+                append(b'<directory file_id="%s" name="%s"%s />\n' % (
                     encode_and_escape(ie.file_id),
                     encode_and_escape(ie.name),
-                    parent_str, parent_id))
+                    parent_str))
         elif ie.kind == 'symlink':
             if not working:
-                append(b'<symlink file_id="%s name="%s%s%s revision="%s '
-                       b'symlink_target="%s />\n' % (
+                append(b'<symlink file_id="%s" name="%s"%s revision="%s" '
+                       b'symlink_target="%s" />\n' % (
                            encode_and_escape(ie.file_id),
                            encode_and_escape(ie.name),
-                           parent_str, parent_id,
+                           parent_str,
                            encode_and_escape(ie.revision),
                            encode_and_escape(ie.symlink_target)))
             else:
-                append(b'<symlink file_id="%s name="%s%s%s />\n' % (
+                append(b'<symlink file_id="%s" name="%s"%s />\n' % (
                     encode_and_escape(ie.file_id),
                     encode_and_escape(ie.name),
-                    parent_str, parent_id))
+                    parent_str))
         elif ie.kind == 'tree-reference':
             if ie.kind not in supported_kinds:
                 raise errors.UnsupportedInventoryKind(ie.kind)
             if not working:
-                append(b'<tree-reference file_id="%s name="%s%s%s '
-                       b'revision="%s reference_revision="%s />\n' % (
+                append(b'<tree-reference file_id="%s" name="%s"%s '
+                       b'revision="%s" reference_revision="%s" />\n' % (
                            encode_and_escape(ie.file_id),
                            encode_and_escape(ie.name),
-                           parent_str, parent_id,
+                           parent_str,
                            encode_and_escape(ie.revision),
                            encode_and_escape(ie.reference_revision)))
             else:
-                append(b'<tree-reference file_id="%s name="%s%s%s />\n' % (
+                append(b'<tree-reference file_id="%s" name="%s"%s />\n' % (
                     encode_and_escape(ie.file_id),
                     encode_and_escape(ie.name),
-                    parent_str, parent_id))
+                    parent_str))
         else:
             raise errors.UnsupportedInventoryKind(ie.kind)
     append(b'</inventory>\n')

=== modified file 'breezy/plugins/weave_fmt/bzrdir.py'
--- old/breezy/plugins/weave_fmt/bzrdir.py	2018-11-11 04:08:32 +0000
+++ new/breezy/plugins/weave_fmt/bzrdir.py	2020-01-31 02:52:24 +0000
@@ -352,10 +352,10 @@
             for i, rev_id in enumerate(self.converted_revs):
                 self.pb.update(gettext('write revision'), i,
                                len(self.converted_revs))
-                text = serializer_v5.write_revision_to_string(
+                lines = serializer_v5.write_revision_to_lines(
                     self.revisions[rev_id])
                 key = (rev_id,)
-                revision_store.add_lines(key, None, osutils.split_lines(text))
+                revision_store.add_lines(key, None, lines)
         finally:
             self.pb.clear()
 

=== modified file 'breezy/plugins/weave_fmt/xml4.py'
--- old/breezy/plugins/weave_fmt/xml4.py	2018-11-11 04:08:32 +0000
+++ new/breezy/plugins/weave_fmt/xml4.py	2020-01-31 02:55:51 +0000
@@ -145,6 +145,13 @@
                     p.set('revision_sha1', rev.parent_sha1s[i])
         return root
 
+    def write_revision_to_string(self, rev):
+        return tostring(self._pack_revision(rev)) + b'\n'
+
+    def _write_element(self, elt, f):
+        ElementTree(elt).write(f, 'utf-8')
+        f.write(b'\n')
+
     def _unpack_revision(self, elt):
         """XML Element -> Revision object"""
 

=== modified file 'breezy/tests/__init__.py'
--- old/breezy/tests/__init__.py	2019-02-03 23:43:20 +0000
+++ new/breezy/tests/__init__.py	2020-01-31 02:46:03 +0000
@@ -1336,7 +1336,9 @@
         if a == b + ('\n' if isinstance(b, text_type) else b'\n'):
             message = 'second string is missing a final newline.\n'
         raise AssertionError(message
-                             + self._ndiff_strings(a, b))
+                             + self._ndiff_strings(
+                                 a if isinstance(a, text_type) else a.decode(),
+                                 b if isinstance(b, text_type) else b.decode()))
 
     def assertEqualMode(self, mode, mode_test):
         self.assertEqual(mode, mode_test,

=== modified file 'breezy/tests/per_repository_vf/helpers.py'
--- old/breezy/tests/per_repository_vf/helpers.py	2018-11-11 04:08:32 +0000
+++ new/breezy/tests/per_repository_vf/helpers.py	2020-01-31 02:52:38 +0000
@@ -65,10 +65,10 @@
                                               parent_ids=[])
             # Manually add the revision text using the RevisionStore API, with
             # bad parents.
-            rev_text = repo._serializer.write_revision_to_string(revision)
+            lines = repo._serializer.write_revision_to_lines(revision)
             repo.revisions.add_lines((revision.revision_id,),
                                      [(b'incorrect-parent',)],
-                                     osutils.split_lines(rev_text))
+                                     lines)
         except:
             repo.abort_write_group()
             repo.unlock()

=== modified file 'breezy/tests/test_chk_serializer.py'
--- old/breezy/tests/test_chk_serializer.py	2018-11-11 04:08:32 +0000
+++ new/breezy/tests/test_chk_serializer.py	2020-01-31 02:54:23 +0000
@@ -86,8 +86,8 @@
         self.assertEqual(None, rev.timezone)
 
     def assertRoundTrips(self, serializer, orig_rev):
-        text = serializer.write_revision_to_string(orig_rev)
-        new_rev = serializer.read_revision_from_string(text)
+        lines = serializer.write_revision_to_lines(orig_rev)
+        new_rev = serializer.read_revision_from_string(b''.join(lines))
         self.assertEqual(orig_rev, new_rev)
 
     def test_roundtrips_non_ascii(self):

=== modified file 'breezy/tests/test_remote.py'
--- old/breezy/tests/test_remote.py	2019-06-01 02:04:54 +0000
+++ new/breezy/tests/test_remote.py	2020-01-31 02:53:57 +0000
@@ -2862,8 +2862,8 @@
         somerev1.timezone = -60
         somerev1.inventory_sha1 = b"691b39be74c67b1212a75fcb19c433aaed903c2b"
         somerev1.message = "Message"
-        body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
-            somerev1))
+        body = zlib.compress(b''.join(chk_bencode_serializer.write_revision_to_lines(
+            somerev1)))
         # Split up body into two bits to make sure the zlib compression object
         # gets data fed twice.
         client.add_success_response_with_body(

=== modified file 'breezy/tests/test_xml.py'
--- old/breezy/tests/test_xml.py	2019-02-14 05:21:21 +0000
+++ new/breezy/tests/test_xml.py	2020-01-31 02:55:35 +0000
@@ -170,6 +170,22 @@
 </revision>
 """
 
+_expected_rev_v8_complex = b"""<revision committer="Erik B&#229;gfors &lt;erik@foo.net&gt;" format="8" inventory_sha1="e79c31c1deb64c163cf660fdedd476dd579ffd41" revision_id="erik@b&#229;gfors-02" timestamp="1125907235.212" timezone="36000">
+<message>Include &#181;nicode characters
+</message>
+<parents>
+<revision_ref revision_id="erik@b&#229;gfors-01" />
+<revision_ref revision_id="erik@bagfors-02" />
+</parents>
+<properties><property name="bar" />
+<property name="foo">this has a
+newline in it</property>
+</properties>
+</revision>
+"""
+
+
+
 _inventory_utf8_v5 = b"""<inventory file_id="TRE&#233;_ROOT" format="5"
                                    revision_id="erik@b&#229;gfors-02">
 <file file_id="b&#229;r-01"
@@ -320,9 +336,7 @@
         """Check that repacking a revision yields the same information"""
         inp = BytesIO(txt)
         rev = breezy.bzr.xml5.serializer_v5.read_revision(inp)
-        outp = BytesIO()
-        breezy.bzr.xml5.serializer_v5.write_revision(rev, outp)
-        outfile_contents = outp.getvalue()
+        outfile_contents = breezy.bzr.xml5.serializer_v5.write_revision_to_string(rev)
         rev2 = breezy.bzr.xml5.serializer_v5.read_revision(
             BytesIO(outfile_contents))
         self.assertEqual(rev, rev2)
@@ -339,13 +353,11 @@
         # fixed 20051025, revisions should have final newline
         rev = breezy.bzr.xml5.serializer_v5.read_revision_from_string(
             _revision_v5)
-        outp = BytesIO()
-        breezy.bzr.xml5.serializer_v5.write_revision(rev, outp)
-        outfile_contents = outp.getvalue()
+        outfile_contents = breezy.bzr.xml5.serializer_v5.write_revision_to_string(rev)
         self.assertEqual(outfile_contents[-1:], b'\n')
         self.assertEqualDiff(
             outfile_contents,
-            breezy.bzr.xml5.serializer_v5.write_revision_to_string(rev))
+            b''.join(breezy.bzr.xml5.serializer_v5.write_revision_to_lines(rev)))
         self.assertEqualDiff(outfile_contents, _expected_rev_v5)
 
     def test_empty_property_value(self):
@@ -354,7 +366,7 @@
         rev = s_v5.read_revision_from_string(_revision_v5)
         props = {'empty': '', 'one': 'one'}
         rev.properties = props
-        txt = s_v5.write_revision_to_string(rev)
+        txt = b''.join(s_v5.write_revision_to_lines(rev))
         new_rev = s_v5.read_revision_from_string(txt)
         self.assertEqual(props, new_rev.properties)
 
@@ -447,25 +459,33 @@
         """Pack revision to XML v6"""
         rev = breezy.bzr.xml6.serializer_v6.read_revision_from_string(
             _expected_rev_v5)
-        serialized = breezy.bzr.xml6.serializer_v6.write_revision_to_string(
+        serialized = breezy.bzr.xml6.serializer_v6.write_revision_to_lines(
             rev)
-        self.assertEqualDiff(serialized, _expected_rev_v5)
+        self.assertEqualDiff(b''.join(serialized), _expected_rev_v5)
 
     def test_revision_text_v7(self):
         """Pack revision to XML v7"""
         rev = breezy.bzr.xml7.serializer_v7.read_revision_from_string(
             _expected_rev_v5)
-        serialized = breezy.bzr.xml7.serializer_v7.write_revision_to_string(
+        serialized = breezy.bzr.xml7.serializer_v7.write_revision_to_lines(
             rev)
-        self.assertEqualDiff(serialized, _expected_rev_v5)
+        self.assertEqualDiff(b''.join(serialized), _expected_rev_v5)
 
     def test_revision_text_v8(self):
         """Pack revision to XML v8"""
         rev = breezy.bzr.xml8.serializer_v8.read_revision_from_string(
             _expected_rev_v8)
-        serialized = breezy.bzr.xml8.serializer_v8.write_revision_to_string(
-            rev)
-        self.assertEqualDiff(serialized, _expected_rev_v8)
+        serialized = breezy.bzr.xml8.serializer_v8.write_revision_to_lines(
+            rev)
+        self.assertEqualDiff(b''.join(serialized), _expected_rev_v8)
+
+    def test_revision_text_v8_complex(self):
+        """Pack revision to XML v8"""
+        rev = breezy.bzr.xml8.serializer_v8.read_revision_from_string(
+            _expected_rev_v8_complex)
+        serialized = breezy.bzr.xml8.serializer_v8.write_revision_to_lines(
+            rev)
+        self.assertEqualDiff(b''.join(serialized), _expected_rev_v8_complex)
 
     def test_revision_ids_are_utf8(self):
         """Parsed revision_ids should all be utf-8 strings, not unicode."""
@@ -533,24 +553,24 @@
         # are being used in xml attributes, and by returning it now, we have to
         # do fewer string operations later.
         val = breezy.bzr.xml_serializer.encode_and_escape('foo bar')
-        self.assertEqual(b'foo bar"', val)
+        self.assertEqual(b'foo bar', val)
         # The second time should be cached
         val2 = breezy.bzr.xml_serializer.encode_and_escape('foo bar')
         self.assertIs(val2, val)
 
     def test_ascii_with_xml(self):
-        self.assertEqual(b'&amp;&apos;&quot;&lt;&gt;"',
+        self.assertEqual(b'&amp;&apos;&quot;&lt;&gt;',
                          breezy.bzr.xml_serializer.encode_and_escape('&\'"<>'))
 
     def test_utf8_with_xml(self):
         # u'\xb5\xe5&\u062c'
         utf8_str = b'\xc2\xb5\xc3\xa5&\xd8\xac'
-        self.assertEqual(b'&#181;&#229;&amp;&#1580;"',
+        self.assertEqual(b'&#181;&#229;&amp;&#1580;',
                          breezy.bzr.xml_serializer.encode_and_escape(utf8_str))
 
     def test_unicode(self):
         uni_str = u'\xb5\xe5&\u062c'
-        self.assertEqual(b'&#181;&#229;&amp;&#1580;"',
+        self.assertEqual(b'&#181;&#229;&amp;&#1580;',
                          breezy.bzr.xml_serializer.encode_and_escape(uni_str))
 
 

=== modified file 'tools/testr-run.py'
--- old/tools/testr-run.py	2018-06-29 21:21:43 +0000
+++ new/tools/testr-run.py	2020-01-31 02:45:16 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import argparse
 import subprocess
@@ -38,7 +38,7 @@
         if args.load_list:
             py2_tests = []
             py3_tests = []
-            with open(args.load_list, 'r') as f:
+            with open(args.load_list, 'rb') as f:
                 all_tests = parse_list(f.read())
             for testname in all_tests:
                 if testname.startswith("python2."):

