← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:py3-native-string-streams into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:py3-native-string-streams into launchpad:master.

Commit message:
Port native-string streams to six.StringIO

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/387726

Of the streams that were created using StringIO.StringIO or cStringIO.StringIO, a number of them (mainly things like log files) accept native strings on both Python 2 and 3.  Port these to six.StringIO for compatibility with Python 3.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:py3-native-string-streams into launchpad:master.
diff --git a/lib/devscripts/tests/test_sourcecode.py b/lib/devscripts/tests/test_sourcecode.py
index cc8f375..6b538c7 100644
--- a/lib/devscripts/tests/test_sourcecode.py
+++ b/lib/devscripts/tests/test_sourcecode.py
@@ -9,7 +9,6 @@ __metaclass__ = type
 
 import os
 import shutil
-from StringIO import StringIO
 import tempfile
 import unittest
 
@@ -21,6 +20,7 @@ except ImportError:
     from bzrlib.bzrdir import BzrDir
     from bzrlib.tests import TestCase
     from bzrlib.transport import get_transport
+import six
 
 from devscripts import get_launchpad_root
 from devscripts.sourcecode import (
@@ -35,7 +35,7 @@ class TestParseConfigFile(unittest.TestCase):
     """Tests for the config file parser."""
 
     def makeFile(self, contents):
-        return StringIO(contents)
+        return six.StringIO(contents)
 
     def test_empty(self):
         # Parsing an empty config file returns an empty sequence.
diff --git a/lib/lp/archivepublisher/model/ftparchive.py b/lib/lp/archivepublisher/model/ftparchive.py
index 6fe3c3c..c516068 100644
--- a/lib/lp/archivepublisher/model/ftparchive.py
+++ b/lib/lp/archivepublisher/model/ftparchive.py
@@ -4,7 +4,6 @@
 from collections import defaultdict
 import os
 import re
-from StringIO import StringIO
 import time
 
 import scandir
@@ -754,7 +753,7 @@ class FTPArchiveHandler:
         explicitly marked as dirty. dirty_pockets must be a nested
         dictionary of booleans, keyed by distroseries.name then pocket.
         """
-        apt_config = StringIO()
+        apt_config = six.StringIO()
         apt_config.write(CONFIG_HEADER % (self._config.archiveroot,
                                           self._config.overrideroot,
                                           self._config.cacheroot,
@@ -875,7 +874,7 @@ class FTPArchiveHandler:
         except OSError:
             pass
 
-        apt_config = StringIO()
+        apt_config = six.StringIO()
         apt_config.write(CONFIG_HEADER % (self._config.archiveroot,
                                           self._config.overrideroot,
                                           self._config.cacheroot,
diff --git a/lib/lp/bugs/scripts/tests/test_bugnotification.py b/lib/lp/bugs/scripts/tests/test_bugnotification.py
index 99b59c6..38d11d9 100644
--- a/lib/lp/bugs/scripts/tests/test_bugnotification.py
+++ b/lib/lp/bugs/scripts/tests/test_bugnotification.py
@@ -13,10 +13,10 @@ from datetime import (
 import logging
 import re
 from smtplib import SMTPException
-import StringIO
 import unittest
 
 import pytz
+import six
 from testtools.matchers import (
     MatchesRegex,
     Not,
@@ -402,7 +402,7 @@ class TestGetEmailNotifications(TestCase):
         # in place.
 
         # Set up logging so we can later assert that no exceptions are logged.
-        log_output = StringIO.StringIO()
+        log_output = six.StringIO()
         logger = logging.getLogger()
         log_handler = logging.StreamHandler(log_output)
         logger.addHandler(logging.StreamHandler(log_output))
diff --git a/lib/lp/registry/scripts/distributionmirror_prober.py b/lib/lp/registry/scripts/distributionmirror_prober.py
index a6350ca..b1d2df0 100644
--- a/lib/lp/registry/scripts/distributionmirror_prober.py
+++ b/lib/lp/registry/scripts/distributionmirror_prober.py
@@ -10,7 +10,6 @@ from datetime import datetime
 import itertools
 import logging
 import os.path
-from StringIO import StringIO
 
 import OpenSSL
 from OpenSSL.SSL import (
@@ -18,6 +17,7 @@ from OpenSSL.SSL import (
     TLSv1_2_METHOD,
     )
 import requests
+import six
 from six.moves import http_client
 from six.moves.urllib.parse import (
     unquote,
@@ -1037,7 +1037,7 @@ class DistroMirrorProber:
                 continue
 
             probed_mirrors.append(mirror)
-            logfile = StringIO()
+            logfile = six.StringIO()
             logfiles[mirror_id] = logfile
             probe_function(mirror, logfile, unchecked_keys, self.logger,
                            max_parallel, max_parallel_per_host)
diff --git a/lib/lp/registry/tests/test_distributionmirror_prober.py b/lib/lp/registry/tests/test_distributionmirror_prober.py
index c674ac0..38bf7dd 100644
--- a/lib/lp/registry/tests/test_distributionmirror_prober.py
+++ b/lib/lp/registry/tests/test_distributionmirror_prober.py
@@ -9,11 +9,11 @@ __metaclass__ = type
 from datetime import datetime
 import logging
 import os
-from StringIO import StringIO
 
 from fixtures import MockPatchObject
 from lazr.uri import URI
 import responses
+import six
 from six.moves import http_client
 from sqlobject import SQLObjectNotFound
 from testtools.matchers import (
@@ -859,7 +859,7 @@ class TestMirrorCDImageProberCallbacks(TestCaseWithFactory):
         mirror = removeSecurityProxy(
             self.factory.makeMirror(distroseries.distribution))
         callbacks = MirrorCDImageProberCallbacks(
-            mirror, distroseries, 'ubuntu', StringIO())
+            mirror, distroseries, 'ubuntu', six.StringIO())
         return callbacks
 
     def getLogger(self):
@@ -961,7 +961,7 @@ class TestArchiveMirrorProberCallbacks(TestCaseWithFactory):
         component = self.factory.makeComponent()
         callbacks = ArchiveMirrorProberCallbacks(
             mirror, distroseries, PackagePublishingPocket.RELEASE,
-            component, 'foo', StringIO())
+            component, 'foo', six.StringIO())
         return callbacks
 
     def test_failure_propagation(self):
@@ -1050,7 +1050,7 @@ class TestProbeFunctionSemaphores(TestCase):
         # Note that calling this function won't actually probe any mirrors; we
         # need to call reactor.run() to actually start the probing.
         with default_timeout(15.0):
-            probe_cdimage_mirror(mirror, StringIO(), [], logging, 100, 2)
+            probe_cdimage_mirror(mirror, six.StringIO(), [], logging, 100, 2)
         self.assertEqual(0, len(mirror.cdimage_series))
 
     def test_archive_mirror_probe_function(self):
@@ -1086,7 +1086,7 @@ class TestProbeFunctionSemaphores(TestCase):
         mirror2_host = URI(mirror2.base_url).host
         mirror3_host = URI(mirror3.base_url).host
 
-        probe_function(mirror1, StringIO(), [], logging, 100, 2)
+        probe_function(mirror1, six.StringIO(), [], logging, 100, 2)
         # Since we have a single mirror to probe we need to have a single
         # DeferredSemaphore with a limit of max_per_host_requests, to ensure we
         # don't issue too many simultaneous connections on that host.
@@ -1097,7 +1097,7 @@ class TestProbeFunctionSemaphores(TestCase):
         # overall number of requests.
         self.assertEqual(multi_lock.overall_lock.limit, max_requests)
 
-        probe_function(mirror2, StringIO(), [], logging, 100, 2)
+        probe_function(mirror2, six.StringIO(), [], logging, 100, 2)
         # Now we have two mirrors to probe, but they have the same hostname,
         # so we'll still have a single semaphore in host_semaphores.
         self.assertEqual(mirror2_host, mirror1_host)
@@ -1105,7 +1105,7 @@ class TestProbeFunctionSemaphores(TestCase):
         multi_lock = request_manager.host_locks[mirror2_host]
         self.assertEqual(multi_lock.host_lock.limit, max_per_host_requests)
 
-        probe_function(mirror3, StringIO(), [], logging, 100, 2)
+        probe_function(mirror3, six.StringIO(), [], logging, 100, 2)
         # This third mirror is on a separate host, so we'll have a second
         # semaphore added to host_semaphores.
         self.assertTrue(mirror3_host != mirror1_host)
@@ -1117,7 +1117,7 @@ class TestProbeFunctionSemaphores(TestCase):
         # proxy, we'll use the mirror's host as the key to find the semaphore
         # that should be used
         self.pushConfig('launchpad', http_proxy='http://squid.internal:3128/')
-        probe_function(mirror3, StringIO(), [], logging, 100, 2)
+        probe_function(mirror3, six.StringIO(), [], logging, 100, 2)
         self.assertEqual(len(request_manager.host_locks), 2)
 
 
@@ -1150,7 +1150,7 @@ class TestLoggingMixin(TestCase):
 
     def test_logMessage_output(self):
         logger = LoggingMixin()
-        logger.log_file = StringIO()
+        logger.log_file = six.StringIO()
         logger._getTime = self._fake_gettime
         logger.logMessage("Ubuntu Warty Released")
         logger.log_file.seek(0)
@@ -1161,7 +1161,7 @@ class TestLoggingMixin(TestCase):
 
     def test_logMessage_integration(self):
         logger = LoggingMixin()
-        logger.log_file = StringIO()
+        logger.log_file = six.StringIO()
         logger.logMessage("Probing...")
         logger.log_file.seek(0)
         message = logger.log_file.read()
diff --git a/lib/lp/registry/tests/test_prf_finder.py b/lib/lp/registry/tests/test_prf_finder.py
index 32a149a..f37cb1a 100644
--- a/lib/lp/registry/tests/test_prf_finder.py
+++ b/lib/lp/registry/tests/test_prf_finder.py
@@ -4,10 +4,10 @@
 import logging
 import os
 import shutil
-from StringIO import StringIO
 import tempfile
 
 import responses
+import six
 from testtools import ExpectedException
 import transaction
 from zope.component import getUtility
@@ -389,7 +389,7 @@ class HandleReleaseTestCase(TestCase):
         # Test that handleRelease() handles the case where a version can't be
         # parsed from the url given.
         ztm = self.layer.txn
-        output = StringIO()
+        output = six.StringIO()
         logger = logging.getLogger()
         logger.setLevel(logging.INFO)
         logger.addHandler(logging.StreamHandler(output))
diff --git a/lib/lp/scripts/tests/test_garbo.py b/lib/lp/scripts/tests/test_garbo.py
index ad62894..2662c01 100644
--- a/lib/lp/scripts/tests/test_garbo.py
+++ b/lib/lp/scripts/tests/test_garbo.py
@@ -14,10 +14,10 @@ from datetime import (
     )
 import hashlib
 import logging
-from StringIO import StringIO
 import time
 
 from pytz import UTC
+import six
 from storm.exceptions import LostObjectError
 from storm.expr import (
     In,
@@ -431,7 +431,7 @@ class TestGarbo(FakeAdapterMixin, TestCaseWithFactory):
         self.runFrequently()
 
         # Capture garbo log output to tests can examine it.
-        self.log_buffer = StringIO()
+        self.log_buffer = six.StringIO()
         handler = logging.StreamHandler(self.log_buffer)
         self.log.addHandler(handler)
         self.addDetail(
diff --git a/lib/lp/scripts/utilities/warninghandler.py b/lib/lp/scripts/utilities/warninghandler.py
index cb0c4ff..fb6373b 100644
--- a/lib/lp/scripts/utilities/warninghandler.py
+++ b/lib/lp/scripts/utilities/warninghandler.py
@@ -8,7 +8,6 @@ from __future__ import print_function
 __metaclass__ = type
 
 import inspect
-import StringIO
 import sys
 import warnings
 
@@ -163,7 +162,7 @@ def launchpad_showwarning(message, category, filename, lineno, file=None,
                           line=None):
     if file is None:
         file = sys.stderr
-    stream = StringIO.StringIO()
+    stream = six.StringIO()
     old_show_warning(message, category, filename, lineno, stream, line=line)
     warning_message = stream.getvalue()
     important_info = find_important_info()
diff --git a/lib/lp/services/job/tests/test_celeryjob.py b/lib/lp/services/job/tests/test_celeryjob.py
index 85f6252..e96c5b4 100644
--- a/lib/lp/services/job/tests/test_celeryjob.py
+++ b/lib/lp/services/job/tests/test_celeryjob.py
@@ -1,11 +1,11 @@
 # Copyright 2012-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from cStringIO import StringIO
 import sys
 from time import sleep
 
 from lazr.jobrunner.bin.clear_queues import clear_queues
+import six
 from testtools.content import Content
 from testtools.content_type import UTF8_TEXT
 
@@ -150,8 +150,8 @@ class TestRunMissingJobs(TestCaseWithFactory):
         try:
             real_stdout = sys.stdout
             real_stderr = sys.stderr
-            sys.stdout = fake_stdout = StringIO()
-            sys.stderr = fake_stderr = StringIO()
+            sys.stdout = fake_stdout = six.StringIO()
+            sys.stderr = fake_stderr = six.StringIO()
             clear_queues(
                 ['script_name', '-c', 'lp.services.job.celeryconfig',
                  result_queue_name])
diff --git a/lib/lp/services/log/logger.py b/lib/lp/services/log/logger.py
index cd83d35..4451a04 100644
--- a/lib/lp/services/log/logger.py
+++ b/lib/lp/services/log/logger.py
@@ -15,10 +15,11 @@ __all__ = [
     ]
 
 import logging
-from StringIO import StringIO
 import sys
 import traceback
 
+import six
+
 from lp.services.log import loglevels
 
 
@@ -198,7 +199,7 @@ class BufferLogger(FakeLogger):
     # service.
 
     def __init__(self):
-        super(BufferLogger, self).__init__(StringIO())
+        super(BufferLogger, self).__init__(six.StringIO())
 
     def getLogBuffer(self):
         """Return the existing log messages."""
@@ -206,7 +207,7 @@ class BufferLogger(FakeLogger):
 
     def clearLogBuffer(self):
         """Clear out the existing log messages."""
-        self.output_file = StringIO()
+        self.output_file = six.StringIO()
 
     def getLogBufferAndClear(self):
         """Return the existing log messages and clear the buffer."""
diff --git a/lib/lp/services/mail/tests/test_dkim.py b/lib/lp/services/mail/tests/test_dkim.py
index a8b2383..26d3d33 100644
--- a/lib/lp/services/mail/tests/test_dkim.py
+++ b/lib/lp/services/mail/tests/test_dkim.py
@@ -6,10 +6,10 @@
 __metaclass__ = type
 
 import logging
-from StringIO import StringIO
 
 import dkim
 import dkim.dnsplug
+import six
 
 from lp.services.features.testing import FeatureFixture
 from lp.services.identity.interfaces.account import AccountStatus
@@ -69,7 +69,7 @@ class TestDKIM(TestCaseWithFactory):
     def setUp(self):
         # Login with admin roles as we aren't testing access here.
         TestCaseWithFactory.setUp(self, 'admin@xxxxxxxxxxxxx')
-        self._log_output = StringIO()
+        self._log_output = six.StringIO()
         handler = logging.StreamHandler(self._log_output)
         self.logger = logging.getLogger('mail-authenticate-dkim')
         self.logger.addHandler(handler)
diff --git a/lib/lp/services/testing/tests/test_parallel.py b/lib/lp/services/testing/tests/test_parallel.py
index 2dec513..f8afd34 100644
--- a/lib/lp/services/testing/tests/test_parallel.py
+++ b/lib/lp/services/testing/tests/test_parallel.py
@@ -5,14 +5,15 @@
 
 __metaclass__ = type
 
-from StringIO import StringIO
 import subprocess
 import tempfile
+from textwrap import dedent
 
 from fixtures import (
     PopenFixture,
     TestWithFixtures,
     )
+import six
 from testtools import (
     TestCase,
     TestResult,
@@ -40,7 +41,7 @@ class TestListTestCase(TestCase, TestWithFixtures):
             with open(load_list, 'rt') as testlist:
                 contents = testlist.readlines()
             self.assertEqual(['foo\n', 'bar\n'], contents)
-            return {'stdout': StringIO(''), 'stdin': StringIO()}
+            return {'stdout': six.StringIO(), 'stdin': six.StringIO()}
         popen = self.useFixture(PopenFixture(check_list_file))
         case = ListTestCase(['foo', 'bar'], ['bin/test'])
         self.assertEqual([], popen.procs)
@@ -102,12 +103,15 @@ class TestUtilities(TestCase, TestWithFixtures):
             self.assertEqual(
                 ['bin/test', '-vt', 'filter', '--list-tests', '--subunit'],
                 args['args'])
-            return {'stdin': StringIO(), 'stdout': StringIO("""\
-test: quux
-successful: quux
-test: glom
-successful: glom
-""")}
+            return {
+                'stdin': six.StringIO(),
+                'stdout': six.StringIO(six.ensure_str(dedent("""\
+                    test: quux
+                    successful: quux
+                    test: glom
+                    successful: glom
+                    """))),
+                }
         self.useFixture(PopenFixture(inject_testlist))
         self.assertEqual(
             ['quux', 'glom'],
diff --git a/lib/lp/services/tests/test_looptuner.py b/lib/lp/services/tests/test_looptuner.py
index aa99afb..2d4c05c 100644
--- a/lib/lp/services/tests/test_looptuner.py
+++ b/lib/lp/services/tests/test_looptuner.py
@@ -8,8 +8,7 @@ These are the edge test cases that don't belong in the doctest.
 
 __metaclass__ = type
 
-from cStringIO import StringIO
-
+import six
 from zope.interface import implementer
 
 from lp.services.log.logger import FakeLogger
@@ -64,7 +63,7 @@ class TestSomething(TestCase):
 
         Exception from cleanup raised.
         """
-        log_file = StringIO()
+        log_file = six.StringIO()
         loop = FailingLoop(fail_cleanup=True)
         tuner = LoopTuner(loop, 5, log=FakeLogger(log_file))
         self.assertRaises(CleanupException, tuner.run)
@@ -76,7 +75,7 @@ class TestSomething(TestCase):
         Exception from cleanup is logged.
         Original exception from main task is raised.
         """
-        log_file = StringIO()
+        log_file = six.StringIO()
         loop = FailingLoop(fail_main=True, fail_cleanup=True)
         tuner = LoopTuner(loop, 5, log=FakeLogger(log_file))
         self.assertRaises(MainException, tuner.run)
diff --git a/lib/lp/services/twistedsupport/__init__.py b/lib/lp/services/twistedsupport/__init__.py
index 778a3eb..16b9953 100644
--- a/lib/lp/services/twistedsupport/__init__.py
+++ b/lib/lp/services/twistedsupport/__init__.py
@@ -20,9 +20,9 @@ from signal import (
     SIGCHLD,
     signal,
     )
-import StringIO
 import sys
 
+import six
 from twisted.internet import (
     defer,
     reactor as default_reactor,
@@ -63,7 +63,7 @@ def suppress_stderr(function):
     @functools.wraps(function)
     def wrapper(*arguments, **keyword_arguments):
         saved_stderr = sys.stderr
-        ignored_stream = StringIO.StringIO()
+        ignored_stream = six.StringIO()
         sys.stderr = ignored_stream
         d = defer.maybeDeferred(function, *arguments, **keyword_arguments)
         return d.addBoth(set_stderr, saved_stderr)
diff --git a/lib/lp/services/utils.py b/lib/lp/services/utils.py
index ea661c5..56e5468 100644
--- a/lib/lp/services/utils.py
+++ b/lib/lp/services/utils.py
@@ -41,7 +41,6 @@ from itertools import (
 import os
 import re
 import string
-from StringIO import StringIO
 import sys
 from textwrap import dedent
 from types import FunctionType
@@ -266,8 +265,8 @@ class CapturedOutput(Fixture):
 
     def __init__(self):
         super(CapturedOutput, self).__init__()
-        self.stdout = StringIO()
-        self.stderr = StringIO()
+        self.stdout = six.StringIO()
+        self.stderr = six.StringIO()
 
     def _setUp(self):
         self.useFixture(MonkeyPatch('sys.stdout', self.stdout))
diff --git a/lib/lp/services/webapp/opstats.py b/lib/lp/services/webapp/opstats.py
index ce5f8c9..6b626ce 100644
--- a/lib/lp/services/webapp/opstats.py
+++ b/lib/lp/services/webapp/opstats.py
@@ -3,12 +3,15 @@
 
 """XML-RPC interface for extracting real time stats from the appserver."""
 
+from __future__ import absolute_import, print_function
+
 __metaclass__ = type
 __all__ = ["OpStats"]
 
-from cStringIO import StringIO
 from time import time
 
+import six
+
 from lp.services.webapp import LaunchpadXMLRPCView
 
 
@@ -65,13 +68,13 @@ class OpStats(LaunchpadXMLRPCView):
 
     def __call__(self):
         now = time()
-        out = StringIO()
+        out = six.StringIO()
         for stat_key in sorted(OpStats.stats.keys()):
-            print >> out, '%s:%d@%d' % (
+            print('%s:%d@%d' % (
                     # Make keys more cricket friendly
                     stat_key.replace(' ', '_').replace('-', ''),
                     OpStats.stats[stat_key], now
-                    )
+                    ), file=out)
         self.request.response.setHeader(
                 'Content-Type', 'text/plain; charset=US-ASCII'
                 )
diff --git a/lib/lp/services/webapp/tests/test_statementtracer.py b/lib/lp/services/webapp/tests/test_statementtracer.py
index 30964a4..bb2c6d0 100644
--- a/lib/lp/services/webapp/tests/test_statementtracer.py
+++ b/lib/lp/services/webapp/tests/test_statementtracer.py
@@ -6,10 +6,10 @@
 __metaclass__ = type
 
 from contextlib import contextmanager
-import StringIO
 import sys
 
 from lazr.restful.utils import get_current_browser_request
+import six
 
 from lp.services.osutils import override_environ
 from lp.services.timeline.requesttimeline import get_request_timeline
@@ -25,7 +25,7 @@ from lp.testing.layers import DatabaseFunctionalLayer
 
 @contextmanager
 def stdout():
-    file = StringIO.StringIO()
+    file = six.StringIO()
     original = sys.stdout
     sys.stdout = file
     try:
@@ -36,7 +36,7 @@ def stdout():
 
 @contextmanager
 def stderr():
-    file = StringIO.StringIO()
+    file = six.StringIO()
     original = sys.stderr
     sys.stderr = file
     try:
diff --git a/lib/lp/soyuz/scripts/ppareport.py b/lib/lp/soyuz/scripts/ppareport.py
index 9605c93..94914fe 100644
--- a/lib/lp/soyuz/scripts/ppareport.py
+++ b/lib/lp/soyuz/scripts/ppareport.py
@@ -15,6 +15,7 @@ import operator
 import os
 import sys
 
+import six
 from storm.locals import Join
 from storm.store import Store
 from zope.component import getUtility
@@ -174,7 +175,7 @@ class PPAReportScript(LaunchpadScript):
             if size <= (threshold * limit):
                 continue
             line = "%s | %d | %d\n" % (canonical_url(ppa), limit, size)
-            self.output.write(line.encode('utf-8'))
+            self.output.write(six.ensure_str(line))
         self.output.write('\n')
 
     def reportUserEmails(self):
@@ -187,7 +188,7 @@ class PPAReportScript(LaunchpadScript):
         for user in sorted_people_to_email:
             line = u"%s | %s | %s\n" % (
                 user.name, user.displayname, user.preferredemail.email)
-            self.output.write(line.encode('utf-8'))
+            self.output.write(six.ensure_str(line))
         self.output.write('\n')
 
     @cachedproperty
diff --git a/lib/lp/soyuz/scripts/tests/test_ppareport.py b/lib/lp/soyuz/scripts/tests/test_ppareport.py
index b022b32..58e972b 100644
--- a/lib/lp/soyuz/scripts/tests/test_ppareport.py
+++ b/lib/lp/soyuz/scripts/tests/test_ppareport.py
@@ -7,10 +7,11 @@ __metaclass__ = type
 
 import os
 import shutil
-from StringIO import StringIO
 import tempfile
 import unittest
 
+import six
+
 from lp.services.config import config
 from lp.services.log.logger import BufferLogger
 from lp.services.scripts.base import LaunchpadScriptFailure
@@ -71,7 +72,7 @@ class TestPPAReport(unittest.TestCase):
         if output is None:
 
             def set_test_output():
-                reporter.output = StringIO()
+                reporter.output = six.StringIO()
             reporter.setOutput = set_test_output
 
             reporter.closeOutput = FakeMethod()
diff --git a/lib/lp/testing/__init__.py b/lib/lp/testing/__init__.py
index 81e86f1..d68c1f1 100644
--- a/lib/lp/testing/__init__.py
+++ b/lib/lp/testing/__init__.py
@@ -57,7 +57,6 @@ __all__ = [
     ]
 
 from contextlib import contextmanager
-from cStringIO import StringIO
 from datetime import (
     datetime,
     timedelta,
@@ -369,7 +368,7 @@ class StormStatementRecorder:
         stop_sql_logging()
 
     def __str__(self):
-        out = StringIO()
+        out = six.StringIO()
         print_queries(self.query_data, file=out)
         return out.getvalue()
 
@@ -682,7 +681,7 @@ class TestCase(testtools.TestCase, fixtures.TestWithFixtures):
     @contextmanager
     def expectedLog(self, regex):
         """Expect a log to be written that matches the regex."""
-        output = StringIO()
+        output = six.StringIO()
         handler = logging.StreamHandler(output)
         logger = logging.getLogger()
         logger.addHandler(handler)
@@ -1580,7 +1579,7 @@ def nonblocking_readline(instream, timeout):
     Files must provide a valid fileno() method. This is a test helper
     as it is inefficient and unlikely useful for production code.
     """
-    result = StringIO()
+    result = six.StringIO()
     start = now = time.time()
     deadline = start + timeout
     while (now < deadline and not result.getvalue().endswith('\n')):
diff --git a/lib/lp/translations/scripts/po_export_queue.py b/lib/lp/translations/scripts/po_export_queue.py
index f8bcf9b..b7281a9 100644
--- a/lib/lp/translations/scripts/po_export_queue.py
+++ b/lib/lp/translations/scripts/po_export_queue.py
@@ -9,10 +9,10 @@ __all__ = [
     ]
 
 import os
-from StringIO import StringIO
 import traceback
 
 import psycopg2
+import six
 from zope.component import (
     getAdapter,
     getUtility,
@@ -346,7 +346,7 @@ class ExportResult:
     def addFailure(self):
         """Store an exception that broke the export."""
         # Get the trace back that produced this failure.
-        exception = StringIO()
+        exception = six.StringIO()
         traceback.print_exc(file=exception)
         exception.seek(0)
         # And store it.