← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:stormify-codeimport into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:stormify-codeimport into launchpad:master.

Commit message:
Convert CodeImport and CodeImportMachine to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

I first fixed factory.getUniqueURL to return Unicode, since that's used quite a bit in the code import tests.

The conversion made it a bit more obvious that CodeImportSet.delete has inadequate permissions, but since they were inadequate beforehand too I haven't tried to fix them as part of this conversion.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-codeimport into launchpad:master.
diff --git a/lib/lp/code/mail/tests/test_codeimport.py b/lib/lp/code/mail/tests/test_codeimport.py
index 7230c2e..a155823 100644
--- a/lib/lp/code/mail/tests/test_codeimport.py
+++ b/lib/lp/code/mail/tests/test_codeimport.py
@@ -3,6 +3,8 @@
 
 """Tests for code import related mailings"""
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 from email import message_from_string
 import textwrap
 
diff --git a/lib/lp/code/model/branchcollection.py b/lib/lp/code/model/branchcollection.py
index 9c0b097..7482778 100644
--- a/lib/lp/code/model/branchcollection.py
+++ b/lib/lp/code/model/branchcollection.py
@@ -272,8 +272,8 @@ class GenericBranchCollection:
             cache._associatedSuiteSourcePackages.append(
                 link.suite_sourcepackage)
         for code_import in IStore(CodeImport).find(
-            CodeImport, CodeImport.branchID.is_in(branch_ids)):
-            cache = caches[code_import.branchID]
+                CodeImport, CodeImport.branch_id.is_in(branch_ids)):
+            cache = caches[code_import.branch_id]
             cache.code_import = code_import
 
     @staticmethod
diff --git a/lib/lp/code/model/codeimport.py b/lib/lp/code/model/codeimport.py
index b6eb130..eab7d42 100644
--- a/lib/lp/code/model/codeimport.py
+++ b/lib/lp/code/model/codeimport.py
@@ -3,6 +3,8 @@
 
 """Database classes including and related to CodeImport."""
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 __metaclass__ = type
 
 __all__ = [
@@ -13,12 +15,7 @@ __all__ = [
 from datetime import timedelta
 
 from lazr.lifecycle.event import ObjectCreatedEvent
-from sqlobject import (
-    ForeignKey,
-    IntervalCol,
-    SQLObjectNotFound,
-    StringCol,
-    )
+import pytz
 from storm.expr import (
     And,
     Desc,
@@ -26,10 +23,13 @@ from storm.expr import (
     Select,
     )
 from storm.locals import (
+    DateTime,
     Int,
     Reference,
     ReferenceSet,
     Store,
+    TimeDelta,
+    Unicode,
     )
 from zope.component import getUtility
 from zope.event import notify
@@ -71,34 +71,44 @@ from lp.code.model.codeimportresult import CodeImportResult
 from lp.registry.interfaces.person import validate_public_person
 from lp.services.config import config
 from lp.services.database.constants import DEFAULT
-from lp.services.database.datetimecol import UtcDateTimeCol
-from lp.services.database.enumcol import EnumCol
+from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import SQLBase
+from lp.services.database.stormbase import StormBase
 
 
 @implementer(ICodeImport)
-class CodeImport(SQLBase):
+class CodeImport(StormBase):
     """See `ICodeImport`."""
-    _table = 'CodeImport'
-    _defaultOrder = ['id']
-
-    def __init__(self, target=None, *args, **kwargs):
-        if target is not None:
-            assert 'branch' not in kwargs
-            assert 'repository' not in kwargs
-            if IBranch.providedBy(target):
-                kwargs['branch'] = target
-            elif IGitRepository.providedBy(target):
-                kwargs['git_repository'] = target
-            else:
-                raise AssertionError("Unknown code import target %s" % target)
-        super(CodeImport, self).__init__(*args, **kwargs)
 
-    date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
-    branch = ForeignKey(dbName='branch', foreignKey='Branch', notNull=False)
-    git_repositoryID = Int(name='git_repository', allow_none=True)
-    git_repository = Reference(git_repositoryID, 'GitRepository.id')
+    __storm_table__ = 'CodeImport'
+    __storm_order__ = 'id'
+
+    id = Int(primary=True)
+
+    def __init__(self, registrant, owner, target, review_status, rcs_type=None,
+                 url=None, cvs_root=None, cvs_module=None):
+        super(CodeImport, self).__init__()
+        self.registrant = registrant
+        self.owner = owner
+        if IBranch.providedBy(target):
+            self.branch = target
+            self.git_repository = None
+        elif IGitRepository.providedBy(target):
+            self.branch = None
+            self.git_repository = target
+        else:
+            raise AssertionError("Unknown code import target %s" % target)
+        self.review_status = review_status
+        self.rcs_type = rcs_type
+        self.url = url
+        self.cvs_root = cvs_root
+        self.cvs_module = cvs_module
+
+    date_created = DateTime(tzinfo=pytz.UTC, allow_none=False, default=DEFAULT)
+    branch_id = Int(name='branch', allow_none=True)
+    branch = Reference(branch_id, 'Branch.id')
+    git_repository_id = Int(name='git_repository', allow_none=True)
+    git_repository = Reference(git_repository_id, 'GitRepository.id')
 
     @property
     def target(self):
@@ -108,21 +118,23 @@ class CodeImport(SQLBase):
             assert self.git_repository is not None
             return self.git_repository
 
-    registrant = ForeignKey(
-        dbName='registrant', foreignKey='Person',
-        storm_validator=validate_public_person, notNull=True)
-    owner = ForeignKey(
-        dbName='owner', foreignKey='Person',
-        storm_validator=validate_public_person, notNull=True)
-    assignee = ForeignKey(
-        dbName='assignee', foreignKey='Person',
-        storm_validator=validate_public_person, notNull=False, default=None)
-
-    review_status = EnumCol(schema=CodeImportReviewStatus, notNull=True,
+    registrant_id = Int(
+        name='registrant', allow_none=False, validator=validate_public_person)
+    registrant = Reference(registrant_id, 'Person.id')
+    owner_id = Int(
+        name='owner', allow_none=False, validator=validate_public_person)
+    owner = Reference(owner_id, 'Person.id')
+    assignee_id = Int(
+        name='assignee', allow_none=True, validator=validate_public_person,
+        default=None)
+    assignee = Reference(assignee_id, 'Person.id')
+
+    review_status = DBEnum(
+        enum=CodeImportReviewStatus, allow_none=False,
         default=CodeImportReviewStatus.REVIEWED)
 
-    rcs_type = EnumCol(schema=RevisionControlSystems,
-        notNull=False, default=None)
+    rcs_type = DBEnum(
+        enum=RevisionControlSystems, allow_none=True, default=None)
 
     @property
     def target_rcs_type(self):
@@ -131,14 +143,14 @@ class CodeImport(SQLBase):
         else:
             return TargetRevisionControlSystems.GIT
 
-    cvs_root = StringCol(default=None)
+    cvs_root = Unicode(default=None)
 
-    cvs_module = StringCol(default=None)
+    cvs_module = Unicode(default=None)
 
-    url = StringCol(default=None)
+    url = Unicode(default=None)
 
-    date_last_successful = UtcDateTimeCol(default=None)
-    update_interval = IntervalCol(default=None)
+    date_last_successful = DateTime(tzinfo=pytz.UTC, default=None)
+    update_interval = TimeDelta(default=None)
 
     @property
     def effective_update_interval(self):
@@ -159,8 +171,7 @@ class CodeImport(SQLBase):
         seconds = default_interval_dict.get(self.rcs_type, 21600)
         return timedelta(seconds=seconds)
 
-    import_job = Reference("<primary key>", "CodeImportJob.code_import_id",
-                           on_remote=True)
+    import_job = Reference(id, "CodeImportJob.code_import_id", on_remote=True)
 
     def getImportDetailsForDisplay(self):
         """See `ICodeImport`."""
@@ -187,7 +198,7 @@ class CodeImport(SQLBase):
                 CodeImportJobWorkflow().deletePendingJob(self)
 
     results = ReferenceSet(
-        '<primary key>', 'CodeImportResult.code_import_id',
+        id, 'CodeImportResult.code_import_id',
         order_by=Desc('CodeImportResult.date_job_started'))
 
     @property
@@ -272,6 +283,11 @@ class CodeImport(SQLBase):
             getUtility(ICodeImportJobWorkflow).requestJob(
                 self.import_job, requester)
 
+    def destroySelf(self):
+        if self.import_job is not None:
+            self.import_job.destroySelf()
+        Store.of(self).remove(self)
+
 
 @implementer(ICodeImportSet)
 class CodeImportSet:
@@ -336,6 +352,7 @@ class CodeImportSet:
             rcs_type=rcs_type, url=url,
             cvs_root=cvs_root, cvs_module=cvs_module,
             review_status=review_status)
+        IStore(CodeImport).add(code_import)
 
         getUtility(ICodeImportEventSet).newCreate(code_import, registrant)
         notify(ObjectCreatedEvent(code_import))
@@ -348,21 +365,25 @@ class CodeImportSet:
 
     def delete(self, code_import):
         """See `ICodeImportSet`."""
-        if code_import.import_job is not None:
-            removeSecurityProxy(code_import.import_job).destroySelf()
-        CodeImport.delete(code_import.id)
+        # XXX cjwatson 2020-03-13: This means that in terms of Zope
+        # permissions anyone can delete a code import, which seems
+        # unfortunate, although it appears to have been this way even before
+        # converting CodeImport to Storm.
+        removeSecurityProxy(code_import).destroySelf()
 
     def get(self, id):
         """See `ICodeImportSet`."""
-        try:
-            return CodeImport.get(id)
-        except SQLObjectNotFound:
+        code_import = IStore(CodeImport).get(CodeImport, id)
+        if code_import is None:
             raise NotFoundError(id)
+        return code_import
 
     def getByCVSDetails(self, cvs_root, cvs_module):
         """See `ICodeImportSet`."""
-        return CodeImport.selectOneBy(
-            cvs_root=cvs_root, cvs_module=cvs_module)
+        return IStore(CodeImport).find(
+            CodeImport,
+            CodeImport.cvs_root == cvs_root,
+            CodeImport.cvs_module == cvs_module).one()
 
     def getByURL(self, url, target_rcs_type):
         """See `ICodeImportSet`."""
@@ -378,10 +399,12 @@ class CodeImportSet:
 
     def getByBranch(self, branch):
         """See `ICodeImportSet`."""
-        return CodeImport.selectOneBy(branch=branch)
+        return IStore(CodeImport).find(
+            CodeImport, CodeImport.branch == branch).one()
 
     def getByGitRepository(self, repository):
-        return CodeImport.selectOneBy(git_repository=repository)
+        return IStore(CodeImport).find(
+            CodeImport, CodeImport.git_repository == repository).one()
 
     def search(self, review_status=None, rcs_type=None, target_rcs_type=None):
         """See `ICodeImportSet`."""
diff --git a/lib/lp/code/model/codeimportmachine.py b/lib/lp/code/model/codeimportmachine.py
index 5963220..07d0028 100644
--- a/lib/lp/code/model/codeimportmachine.py
+++ b/lib/lp/code/model/codeimportmachine.py
@@ -12,10 +12,13 @@ __all__ = [
     'CodeImportMachineSet',
     ]
 
-from sqlobject import StringCol
+import pytz
 from storm.locals import (
+    DateTime,
     Desc,
+    Int,
     ReferenceSet,
+    Unicode,
     )
 from zope.component import getUtility
 from zope.interface import implementer
@@ -33,34 +36,44 @@ from lp.services.database.constants import (
     DEFAULT,
     UTC_NOW,
     )
-from lp.services.database.datetimecol import UtcDateTimeCol
-from lp.services.database.enumcol import EnumCol
-from lp.services.database.sqlbase import SQLBase
+from lp.services.database.enumcol import DBEnum
+from lp.services.database.interfaces import IStore
+from lp.services.database.stormbase import StormBase
 
 
 @implementer(ICodeImportMachine)
-class CodeImportMachine(SQLBase):
+class CodeImportMachine(StormBase):
     """See `ICodeImportMachine`."""
 
-    _defaultOrder = ['hostname']
+    __storm_table__ = 'CodeImportMachine'
+    __storm_order__ = 'hostname'
 
-    date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
+    id = Int(primary=True)
 
-    hostname = StringCol(default=None)
-    state = EnumCol(enum=CodeImportMachineState, notNull=True,
+    date_created = DateTime(tzinfo=pytz.UTC, allow_none=False, default=DEFAULT)
+
+    hostname = Unicode(allow_none=False)
+    state = DBEnum(
+        enum=CodeImportMachineState, allow_none=False,
         default=CodeImportMachineState.OFFLINE)
-    heartbeat = UtcDateTimeCol(notNull=False)
+    heartbeat = DateTime(tzinfo=pytz.UTC, allow_none=True)
 
     current_jobs = ReferenceSet(
-        '<primary key>', 'CodeImportJob.machine_id',
+        id, 'CodeImportJob.machine_id',
         order_by=('CodeImportJob.date_started', 'CodeImportJob.id'))
 
     events = ReferenceSet(
-        '<primary key>', 'CodeImportEvent.machine_id',
+        id, 'CodeImportEvent.machine_id',
         order_by=(
             Desc('CodeImportEvent.date_created'),
             Desc('CodeImportEvent.id')))
 
+    def __init__(self, hostname, heartbeat=None):
+        super(CodeImportMachine, self).__init__()
+        self.hostname = hostname
+        self.heartbeat = heartbeat
+        self.state = CodeImportMachineState.OFFLINE
+
     def shouldLookForJob(self, worker_limit):
         """See `ICodeImportMachine`."""
         job_count = self.current_jobs.count()
@@ -116,11 +129,12 @@ class CodeImportMachineSet(object):
 
     def getAll(self):
         """See `ICodeImportMachineSet`."""
-        return CodeImportMachine.select()
+        return IStore(CodeImportMachine).find(CodeImportMachine)
 
     def getByHostname(self, hostname):
         """See `ICodeImportMachineSet`."""
-        return CodeImportMachine.selectOneBy(hostname=hostname)
+        return IStore(CodeImportMachine).find(
+            CodeImportMachine, CodeImportMachine.hostname == hostname).one()
 
     def new(self, hostname, state=CodeImportMachineState.OFFLINE):
         """See `ICodeImportMachineSet`."""
@@ -130,4 +144,5 @@ class CodeImportMachineSet(object):
         elif state != CodeImportMachineState.OFFLINE:
             raise AssertionError(
                 "Invalid machine creation state: %r." % state)
+        IStore(CodeImportMachine).add(machine)
         return machine
diff --git a/lib/lp/code/model/gitcollection.py b/lib/lp/code/model/gitcollection.py
index 3b512f7..cf17a0b 100644
--- a/lib/lp/code/model/gitcollection.py
+++ b/lib/lp/code/model/gitcollection.py
@@ -211,8 +211,9 @@ class GenericGitCollection:
         for cache in caches.values():
             cache.code_import = None
         for code_import in IStore(CodeImport).find(
-                CodeImport, CodeImport.git_repositoryID.is_in(repository_ids)):
-            caches[code_import.git_repositoryID].code_import = code_import
+                CodeImport,
+                CodeImport.git_repository_id.is_in(repository_ids)):
+            caches[code_import.git_repository_id].code_import = code_import
 
     @staticmethod
     def _convertListingSortToOrderBy(sort_by):
diff --git a/lib/lp/codehosting/codeimport/foreigntree.py b/lib/lp/codehosting/codeimport/foreigntree.py
index 42df3af..0e9b292 100644
--- a/lib/lp/codehosting/codeimport/foreigntree.py
+++ b/lib/lp/codehosting/codeimport/foreigntree.py
@@ -9,6 +9,7 @@ __all__ = ['CVSWorkingTree']
 import os
 
 import CVS
+import six
 
 
 class CVSWorkingTree:
@@ -21,8 +22,8 @@ class CVSWorkingTree:
         :param cvs_module: The module in the CVS repository.
         :param local_path: The local path to check the working tree out to.
         """
-        self.root = cvs_root
-        self.module = cvs_module
+        self.root = six.ensure_str(cvs_root)
+        self.module = six.ensure_str(cvs_module)
         self.local_path = os.path.abspath(local_path)
 
     def checkout(self):
diff --git a/lib/lp/codehosting/codeimport/tests/servers.py b/lib/lp/codehosting/codeimport/tests/servers.py
index 20388fc..3a872cb 100644
--- a/lib/lp/codehosting/codeimport/tests/servers.py
+++ b/lib/lp/codehosting/codeimport/tests/servers.py
@@ -55,6 +55,7 @@ from dulwich.web import (
     WSGIRequestHandlerLogger,
     WSGIServerLogger,
     )
+import six
 from subvertpy import SubversionException
 import subvertpy.ra
 import subvertpy.repos
@@ -68,7 +69,7 @@ def local_path_to_url(local_path):
     This implementation is unusual in that it returns a file://localhost/ URL.
     This is to work around the valid_vcs_details constraint on CodeImport.
     """
-    return 'file://localhost' + escape(
+    return u'file://localhost' + escape(
         os.path.normpath(os.path.abspath(local_path)))
 
 
@@ -113,7 +114,7 @@ class SubversionServer(Server):
     def get_url(self):
         """Return a URL to the Subversion repository."""
         if self._use_svn_serve:
-            return 'svn://localhost/'
+            return u'svn://localhost/'
         else:
             return local_path_to_url(self.repository_path)
 
@@ -226,7 +227,7 @@ class CVSServer(Server):
 
     def getRoot(self):
         """Return the CVS root for this server."""
-        return self._repository_path
+        return six.ensure_text(self._repository_path)
 
     @run_in_temporary_directory
     def makeModule(self, module_name, tree_contents):
@@ -332,7 +333,7 @@ class GitServer(Server):
         """Return a URL to the Git repository."""
         if self._use_server:
             host, port = self._server.get_address()
-            return 'http://%s:%d/%s' % (host, port, repository_name)
+            return u'http://%s:%d/%s' % (host, port, repository_name)
         else:
             return local_path_to_url(
                 os.path.join(self.repository_store, repository_name))
@@ -395,7 +396,7 @@ class BzrServer(Server):
 
     def get_url(self):
         if self._use_server:
-            return self._bzrserver.get_url()
+            return six.ensure_text(self._bzrserver.get_url())
         else:
             return local_path_to_url(self.repository_path)
 
diff --git a/lib/lp/codehosting/codeimport/tests/test_worker.py b/lib/lp/codehosting/codeimport/tests/test_worker.py
index b414030..30a3e11 100644
--- a/lib/lp/codehosting/codeimport/tests/test_worker.py
+++ b/lib/lp/codehosting/codeimport/tests/test_worker.py
@@ -54,6 +54,7 @@ from CVS import (
 from dulwich.repo import Repo as GitRepo
 from fixtures import FakeLogger
 import scandir
+import six
 import subvertpy
 import subvertpy.client
 import subvertpy.ra
@@ -962,8 +963,9 @@ class TestCVSImport(WorkerTest, CSCVSActualImportMixin):
         # If you write to a file in the same second as the previous commit,
         # CVS will not think that it has changed.
         time.sleep(1)
-        repo = Repository(source_details.cvs_root, BufferLogger())
-        repo.get(source_details.cvs_module, 'working_dir')
+        repo = Repository(
+            six.ensure_str(source_details.cvs_root), BufferLogger())
+        repo.get(six.ensure_str(source_details.cvs_module), 'working_dir')
         wt = CVSTree('working_dir')
         self.build_tree_contents([('working_dir/README', 'New content')])
         wt.commit(log='Log message')
@@ -1243,7 +1245,7 @@ class TestBzrSvnImport(WorkerTest, SubversionImportHelpers,
         cache_dir_contents = os.listdir(cache_dir)
         self.assertNotEqual([], cache_dir_contents)
         opener = BranchOpener(worker._opener_policy, worker.probers)
-        remote_branch = opener.open(worker.source_details.url)
+        remote_branch = opener.open(six.ensure_str(worker.source_details.url))
         worker.pushBazaarBranch(
             self.make_branch('.'), remote_branch=remote_branch)
         worker.import_data_store.fetch('svn-cache.tar.gz')
diff --git a/lib/lp/codehosting/codeimport/tests/test_workermonitor.py b/lib/lp/codehosting/codeimport/tests/test_workermonitor.py
index d2422cb..634a741 100644
--- a/lib/lp/codehosting/codeimport/tests/test_workermonitor.py
+++ b/lib/lp/codehosting/codeimport/tests/test_workermonitor.py
@@ -672,7 +672,7 @@ def nuke_codeimport_sample_data():
     """Delete all the sample data that might interfere with tests."""
     for job in IStore(CodeImportJob).find(CodeImportJob):
         job.destroySelf()
-    for code_import in CodeImport.select():
+    for code_import in IStore(CodeImport).find(CodeImport):
         code_import.destroySelf()
 
 
@@ -736,7 +736,7 @@ class TestWorkerMonitorIntegration(TestCaseInTempDir, TestCase):
         self.foreign_commit_count = 2
 
         return self.factory.makeCodeImport(
-            cvs_root=cvs_server.getRoot(), cvs_module='trunk')
+            cvs_root=cvs_server.getRoot(), cvs_module=u'trunk')
 
     def makeSVNCodeImport(self):
         """Make a `CodeImport` that points to a real Subversion repository."""
diff --git a/lib/lp/codehosting/codeimport/worker.py b/lib/lp/codehosting/codeimport/worker.py
index e20a6f3..6259189 100644
--- a/lib/lp/codehosting/codeimport/worker.py
+++ b/lib/lp/codehosting/codeimport/worker.py
@@ -74,6 +74,7 @@ from lazr.uri import (
     )
 from pymacaroons import Macaroon
 import SCM
+import six
 from six.moves.urllib.parse import (
     urlsplit,
     urlunsplit,
@@ -736,7 +737,8 @@ class PullingImportWorker(ToBzrImportWorker):
                 "Getting exising bzr branch from central store.")
             bazaar_branch = self.getBazaarBranch()
             try:
-                remote_branch = opener.open(self.source_details.url)
+                remote_branch = opener.open(
+                    six.ensure_str(self.source_details.url))
             except TooManyRedirections:
                 self._logger.info("Too many redirections.")
                 return CodeImportWorkerExitCode.FAILURE_INVALID
diff --git a/lib/lp/oci/tests/test_ocirecipe.py b/lib/lp/oci/tests/test_ocirecipe.py
index 35bdab9..b2d2045 100644
--- a/lib/lp/oci/tests/test_ocirecipe.py
+++ b/lib/lp/oci/tests/test_ocirecipe.py
@@ -412,7 +412,7 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory):
     def test_newPushRule(self):
         self.setConfig()
         recipe = self.factory.makeOCIRecipe()
-        url = ensure_text(self.factory.getUniqueURL())
+        url = self.factory.getUniqueURL()
         image_name = ensure_text(self.factory.getUniqueString())
         credentials = {
             "username": "test-username", "password": "test-password"}
@@ -452,7 +452,7 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory):
     def test_newPushRule_same_details(self):
         self.setConfig()
         recipe = self.factory.makeOCIRecipe()
-        url = ensure_text(self.factory.getUniqueURL())
+        url = self.factory.getUniqueURL()
         image_name = ensure_text(self.factory.getUniqueString())
         credentials = {
             "username": "test-username", "password": "test-password"}
diff --git a/lib/lp/oci/tests/test_ociregistrycredentials.py b/lib/lp/oci/tests/test_ociregistrycredentials.py
index bee4e4b..6e42b28 100644
--- a/lib/lp/oci/tests/test_ociregistrycredentials.py
+++ b/lib/lp/oci/tests/test_ociregistrycredentials.py
@@ -140,7 +140,7 @@ class TestOCIRegistryCredentialsSet(OCIConfigHelperMixin, TestCaseWithFactory):
 
     def test_new(self):
         owner = self.factory.makePerson()
-        url = unicode(self.factory.getUniqueURL())
+        url = self.factory.getUniqueURL()
         credentials = {'username': 'foo', 'password': 'bar'}
         oci_credentials = getUtility(IOCIRegistryCredentialsSet).new(
             owner=owner,
@@ -152,7 +152,7 @@ class TestOCIRegistryCredentialsSet(OCIConfigHelperMixin, TestCaseWithFactory):
 
     def test_new_with_existing(self):
         owner = self.factory.makePerson()
-        url = unicode(self.factory.getUniqueURL())
+        url = self.factory.getUniqueURL()
         credentials = {'username': 'foo', 'password': 'bar'}
         getUtility(IOCIRegistryCredentialsSet).new(
             owner=owner,
@@ -165,7 +165,7 @@ class TestOCIRegistryCredentialsSet(OCIConfigHelperMixin, TestCaseWithFactory):
 
     def test_getOrCreate_existing(self):
         owner = self.factory.makePerson()
-        url = unicode(self.factory.getUniqueURL())
+        url = self.factory.getUniqueURL()
         credentials = {'username': 'foo', 'password': 'bar'}
         new = getUtility(IOCIRegistryCredentialsSet).new(
             owner=owner,
@@ -181,7 +181,7 @@ class TestOCIRegistryCredentialsSet(OCIConfigHelperMixin, TestCaseWithFactory):
 
     def test_getOrCreate_new(self):
         owner = self.factory.makePerson()
-        url = unicode(self.factory.getUniqueURL())
+        url = self.factory.getUniqueURL()
         credentials = {'username': 'foo', 'password': 'bar'}
         new = getUtility(IOCIRegistryCredentialsSet).getOrCreate(
             owner=owner,
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index c04041d..00227ed 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -503,8 +503,8 @@ class ObjectFactory(
         if scheme is None:
             scheme = 'http'
         if host is None:
-            host = "%s.domain.com" % self.getUniqueString('domain')
-        return '%s://%s/%s' % (scheme, host, self.getUniqueString('path'))
+            host = "%s.domain.com" % self.getUniqueUnicode('domain')
+        return '%s://%s/%s' % (scheme, host, self.getUniqueUnicode('path'))
 
     def getUniqueDate(self):
         """Return a unique date since January 1 2009.
@@ -532,9 +532,9 @@ class ObjectFactory(
         elif rcstype == 'cvs':
             assert url is None
             if cvs_root is None:
-                cvs_root = self.getUniqueString()
+                cvs_root = self.getUniqueUnicode()
             if cvs_module is None:
-                cvs_module = self.getUniqueString()
+                cvs_module = self.getUniqueUnicode()
         elif rcstype == 'git':
             assert cvs_root is cvs_module is None
             if url is None:
@@ -1841,7 +1841,7 @@ class BareLaunchpadObjectFactory(ObjectFactory):
     def makeGitRefRemote(self, repository_url=None, path=None):
         """Create an object representing a ref in a remote repository."""
         if repository_url is None:
-            repository_url = self.getUniqueURL().decode('utf-8')
+            repository_url = self.getUniqueURL()
         if path is None:
             path = self.getUniqueString('refs/heads/path').decode('utf-8')
         return getUtility(IGitRefRemoteSet).new(repository_url, path)
@@ -2526,7 +2526,7 @@ class BareLaunchpadObjectFactory(ObjectFactory):
 
         The machine will be in the OFFLINE state."""
         if hostname is None:
-            hostname = self.getUniqueString('machine-')
+            hostname = self.getUniqueUnicode('machine-')
         if set_online:
             state = CodeImportMachineState.ONLINE
         else:
@@ -4784,7 +4784,7 @@ class BareLaunchpadObjectFactory(ObjectFactory):
         if target is None:
             target = self.makeGitRepository()
         if delivery_url is None:
-            delivery_url = self.getUniqueURL().decode('utf-8')
+            delivery_url = self.getUniqueURL()
         return getUtility(IWebhookSet).new(
             target, self.makePerson(), delivery_url, event_types or [],
             active, secret)
@@ -5078,7 +5078,7 @@ class BareLaunchpadObjectFactory(ObjectFactory):
         if owner is None:
             owner = self.makePerson()
         if url is None:
-            url = six.ensure_text(self.getUniqueURL())
+            url = self.getUniqueURL()
         if credentials is None:
             credentials = {
                 'username': self.getUniqueUnicode(),