def example(request, data): # This is a test fixture. You will want to write your own, and # delete this one. See the parallel # standard_yuixhr_test_template.js to see how to call fixtures # from your Javascript tests. # # A test fixture prepares the application for your test. You can # do whatever you need here, including creating objects with an # object factory and logging the browser in as a given user. # You'll see an example below. # # Test fixtures can also return information back to your test. # Simply stuff the information you want into the "data" dict. It # will be converted into JSON and sent back to the Javascript # caller. Even Launchpad objects are converted, using the # standard lazr.restful mechanism. This can be useful in several # ways. Here are three examples. # # First, you can communicate information about the objects you # have created in the setup so that the Javascript knows what URLs # to use. The code in this function has an example of this, # below. # # Second, you can return information about verifying some aspect # of the database state, so your Javascript test can easily assert # some fact that is not usually easily exposed to it. # # Finally, you can stash information that your teardown might # need. You shouldn't usually need to clean anything up, because # the database and librarian are reset after every test, but if # you do set something up that needs an explicit teardown, you can # stash JSON-serializable information in "data" that the teardown # can use to know what to clean up. # # You can compose these setups and teardowns as well, using .extend. # There is a small example of this as well, below. # # As a full example, we will create an administrator and another # person; we will have the administrator create an object; we will # log the browser in as the other person; and we will stash # information about the object and the two people in the data # object. # # Again, this is a semi-random example. Rip this whole fixture # out, and write the ones that you need. factory = LaunchpadObjectFactory() data['admin'] = factory.makeAdministrator() data['user'] = factory.makePerson() with person_logged_in(data['admin']): data['product'] = factory.makeProduct(owner=data['admin']) # This logs the browser in as a given person. You need to use # this function for that purpose--the standard lp.testing login # functions are insufficient. login_as_person(data['user']) # Now we've done everything we said we would. Let's imagine that # we had to also write some file to disk that would need to be # cleaned up at the end of the test. We might stash information # about that in "data" too. data['some random data we might need for cleaning up'] = 'rutebega'
class TestWorkerMonitorIntegration(BzrTestCase): layer = ZopelessAppServerLayer run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=60) def setUp(self): BzrTestCase.setUp(self) login('*****@*****.**') self.factory = LaunchpadObjectFactory() nuke_codeimport_sample_data() self.repo_path = tempfile.mkdtemp() self.disable_directory_isolation() self.addCleanup(shutil.rmtree, self.repo_path) self.foreign_commit_count = 0 def tearDown(self): BzrTestCase.tearDown(self) logout() def makeCVSCodeImport(self): """Make a `CodeImport` that points to a real CVS repository.""" cvs_server = CVSServer(self.repo_path) cvs_server.start_server() self.addCleanup(cvs_server.stop_server) cvs_server.makeModule('trunk', [('README', 'original\n')]) self.foreign_commit_count = 2 return self.factory.makeCodeImport( cvs_root=cvs_server.getRoot(), cvs_module='trunk') def makeSVNCodeImport(self): """Make a `CodeImport` that points to a real Subversion repository.""" self.subversion_server = SubversionServer(self.repo_path) self.subversion_server.start_server() self.addCleanup(self.subversion_server.stop_server) url = self.subversion_server.makeBranch( 'trunk', [('README', 'contents')]) self.foreign_commit_count = 2 return self.factory.makeCodeImport(svn_branch_url=url) def makeBzrSvnCodeImport(self): """Make a `CodeImport` that points to a real Subversion repository.""" self.subversion_server = SubversionServer( self.repo_path, use_svn_serve=True) self.subversion_server.start_server() self.addCleanup(self.subversion_server.stop_server) url = self.subversion_server.makeBranch( 'trunk', [('README', 'contents')]) self.foreign_commit_count = 2 return self.factory.makeCodeImport( svn_branch_url=url, rcs_type=RevisionControlSystems.BZR_SVN) def makeGitCodeImport(self): """Make a `CodeImport` that points to a real Git repository.""" self.git_server = GitServer(self.repo_path, use_server=False) self.git_server.start_server() self.addCleanup(self.git_server.stop_server) self.git_server.makeRepo([('README', 'contents')]) self.foreign_commit_count = 1 return self.factory.makeCodeImport( git_repo_url=self.git_server.get_url()) def makeBzrCodeImport(self): """Make a `CodeImport` that points to a real Bazaar branch.""" self.bzr_server = BzrServer(self.repo_path) self.bzr_server.start_server() self.addCleanup(self.bzr_server.stop_server) self.bzr_server.makeRepo([('README', 'contents')]) self.foreign_commit_count = 1 return self.factory.makeCodeImport( bzr_branch_url=self.bzr_server.get_url()) def getStartedJobForImport(self, code_import): """Get a started `CodeImportJob` for `code_import`. This method approves the import, creates a job, marks it started and returns the job. It also makes sure there are no branches or foreign trees in the default stores to interfere with processing this job. """ source_details = CodeImportSourceDetails.fromCodeImport(code_import) clean_up_default_stores_for_import(source_details) self.addCleanup(clean_up_default_stores_for_import, source_details) if code_import.review_status != CodeImportReviewStatus.REVIEWED: code_import.updateFromData( {'review_status': CodeImportReviewStatus.REVIEWED}, self.factory.makePerson()) job = getUtility(ICodeImportJobSet).getJobForMachine('machine', 10) self.assertEqual(code_import, job.code_import) return job def assertCodeImportResultCreated(self, code_import): """Assert that a `CodeImportResult` was created for `code_import`.""" self.assertEqual(len(list(code_import.results)), 1) result = list(code_import.results)[0] self.assertEqual(result.status, CodeImportResultStatus.SUCCESS) def assertBranchImportedOKForCodeImport(self, code_import): """Assert that a branch was pushed into the default branch store.""" url = get_default_bazaar_branch_store()._getMirrorURL( code_import.branch.id) branch = Branch.open(url) self.assertEqual(self.foreign_commit_count, branch.revno()) def assertImported(self, ignored, code_import_id): """Assert that the `CodeImport` of the given id was imported.""" # In the in-memory tests, check that resetTimeout on the # CodeImportWorkerMonitorProtocol was called at least once. if self._protocol is not None: self.assertPositive(self._protocol.reset_calls) code_import = getUtility(ICodeImportSet).get(code_import_id) self.assertCodeImportResultCreated(code_import) self.assertBranchImportedOKForCodeImport(code_import) def performImport(self, job_id): """Perform the import job with ID job_id. Return a Deferred that fires when it the job is done. This implementation does it in-process. """ logger = BufferLogger() monitor = CIWorkerMonitorForTesting( job_id, logger, xmlrpc.Proxy(config.codeimportdispatcher.codeimportscheduler_url), "anything") deferred = monitor.run() def save_protocol_object(result): """Save the process protocol object. We do this in an addBoth so that it's called after the process protocol is actually constructed but before we drop the last reference to the monitor object. """ self._protocol = monitor._protocol return result return deferred.addBoth(save_protocol_object) def test_import_cvs(self): # Create a CVS CodeImport and import it. job = self.getStartedJobForImport(self.makeCVSCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() result = self.performImport(job_id) return result.addCallback(self.assertImported, code_import_id) def test_import_subversion(self): # Create a Subversion CodeImport and import it. job = self.getStartedJobForImport(self.makeSVNCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() result = self.performImport(job_id) return result.addCallback(self.assertImported, code_import_id) def test_import_git(self): # Create a Git CodeImport and import it. job = self.getStartedJobForImport(self.makeGitCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() result = self.performImport(job_id) return result.addCallback(self.assertImported, code_import_id) def test_import_bzr(self): # Create a Bazaar CodeImport and import it. job = self.getStartedJobForImport(self.makeBzrCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() result = self.performImport(job_id) return result.addCallback(self.assertImported, code_import_id) def test_import_bzrsvn(self): # Create a Subversion-via-bzr-svn CodeImport and import it. job = self.getStartedJobForImport(self.makeBzrSvnCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() result = self.performImport(job_id) return result.addCallback(self.assertImported, code_import_id)
class TestWorkerMonitorIntegration(BzrTestCase): layer = ZopelessAppServerLayer run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=60) def setUp(self): BzrTestCase.setUp(self) login('*****@*****.**') self.factory = LaunchpadObjectFactory() nuke_codeimport_sample_data() self.repo_path = tempfile.mkdtemp() self.disable_directory_isolation() self.addCleanup(shutil.rmtree, self.repo_path) self.foreign_commit_count = 0 def tearDown(self): BzrTestCase.tearDown(self) logout() def makeCVSCodeImport(self): """Make a `CodeImport` that points to a real CVS repository.""" cvs_server = CVSServer(self.repo_path) cvs_server.start_server() self.addCleanup(cvs_server.stop_server) cvs_server.makeModule('trunk', [('README', 'original\n')]) self.foreign_commit_count = 2 return self.factory.makeCodeImport(cvs_root=cvs_server.getRoot(), cvs_module='trunk') def makeSVNCodeImport(self): """Make a `CodeImport` that points to a real Subversion repository.""" self.subversion_server = SubversionServer(self.repo_path) self.subversion_server.start_server() self.addCleanup(self.subversion_server.stop_server) url = self.subversion_server.makeBranch('trunk', [('README', 'contents')]) self.foreign_commit_count = 2 return self.factory.makeCodeImport(svn_branch_url=url) def makeBzrSvnCodeImport(self): """Make a `CodeImport` that points to a real Subversion repository.""" self.subversion_server = SubversionServer(self.repo_path, use_svn_serve=True) self.subversion_server.start_server() self.addCleanup(self.subversion_server.stop_server) url = self.subversion_server.makeBranch('trunk', [('README', 'contents')]) self.foreign_commit_count = 2 return self.factory.makeCodeImport( svn_branch_url=url, rcs_type=RevisionControlSystems.BZR_SVN) def makeGitCodeImport(self): """Make a `CodeImport` that points to a real Git repository.""" self.git_server = GitServer(self.repo_path, use_server=False) self.git_server.start_server() self.addCleanup(self.git_server.stop_server) self.git_server.makeRepo([('README', 'contents')]) self.foreign_commit_count = 1 return self.factory.makeCodeImport( git_repo_url=self.git_server.get_url()) def makeBzrCodeImport(self): """Make a `CodeImport` that points to a real Bazaar branch.""" self.bzr_server = BzrServer(self.repo_path) self.bzr_server.start_server() self.addCleanup(self.bzr_server.stop_server) self.bzr_server.makeRepo([('README', 'contents')]) self.foreign_commit_count = 1 return self.factory.makeCodeImport( bzr_branch_url=self.bzr_server.get_url()) def getStartedJobForImport(self, code_import): """Get a started `CodeImportJob` for `code_import`. This method approves the import, creates a job, marks it started and returns the job. It also makes sure there are no branches or foreign trees in the default stores to interfere with processing this job. """ source_details = CodeImportSourceDetails.fromCodeImport(code_import) clean_up_default_stores_for_import(source_details) self.addCleanup(clean_up_default_stores_for_import, source_details) if code_import.review_status != CodeImportReviewStatus.REVIEWED: code_import.updateFromData( {'review_status': CodeImportReviewStatus.REVIEWED}, self.factory.makePerson()) job = getUtility(ICodeImportJobSet).getJobForMachine('machine', 10) self.assertEqual(code_import, job.code_import) return job def assertCodeImportResultCreated(self, code_import): """Assert that a `CodeImportResult` was created for `code_import`.""" self.assertEqual(len(list(code_import.results)), 1) result = list(code_import.results)[0] self.assertEqual(result.status, CodeImportResultStatus.SUCCESS) def assertBranchImportedOKForCodeImport(self, code_import): """Assert that a branch was pushed into the default branch store.""" url = get_default_bazaar_branch_store()._getMirrorURL( code_import.branch.id) branch = Branch.open(url) self.assertEqual(self.foreign_commit_count, branch.revno()) def assertImported(self, ignored, code_import_id): """Assert that the `CodeImport` of the given id was imported.""" # In the in-memory tests, check that resetTimeout on the # CodeImportWorkerMonitorProtocol was called at least once. if self._protocol is not None: self.assertPositive(self._protocol.reset_calls) code_import = getUtility(ICodeImportSet).get(code_import_id) self.assertCodeImportResultCreated(code_import) self.assertBranchImportedOKForCodeImport(code_import) def performImport(self, job_id): """Perform the import job with ID job_id. Return a Deferred that fires when it the job is done. This implementation does it in-process. """ logger = BufferLogger() monitor = CIWorkerMonitorForTesting( job_id, logger, xmlrpc.Proxy(config.codeimportdispatcher.codeimportscheduler_url), "anything") deferred = monitor.run() def save_protocol_object(result): """Save the process protocol object. We do this in an addBoth so that it's called after the process protocol is actually constructed but before we drop the last reference to the monitor object. """ self._protocol = monitor._protocol return result return deferred.addBoth(save_protocol_object) def test_import_cvs(self): # Create a CVS CodeImport and import it. job = self.getStartedJobForImport(self.makeCVSCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() result = self.performImport(job_id) return result.addCallback(self.assertImported, code_import_id) def test_import_subversion(self): # Create a Subversion CodeImport and import it. job = self.getStartedJobForImport(self.makeSVNCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() result = self.performImport(job_id) return result.addCallback(self.assertImported, code_import_id) def test_import_git(self): # Create a Git CodeImport and import it. job = self.getStartedJobForImport(self.makeGitCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() result = self.performImport(job_id) return result.addCallback(self.assertImported, code_import_id) def test_import_bzr(self): # Create a Bazaar CodeImport and import it. job = self.getStartedJobForImport(self.makeBzrCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() result = self.performImport(job_id) return result.addCallback(self.assertImported, code_import_id) def test_import_bzrsvn(self): # Create a Subversion-via-bzr-svn CodeImport and import it. job = self.getStartedJobForImport(self.makeBzrSvnCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() result = self.performImport(job_id) return result.addCallback(self.assertImported, code_import_id)
class TestRemoveTranslations(TestCase): """Test `remove_translations`.""" layer = LaunchpadZopelessLayer def setUp(self): super(TestRemoveTranslations, self).setUp() # Acquire privileges to delete TranslationMessages. That's not # something we normally do. Actually we should test under # rosettaadmin, but that user does not have all the privileges # needed to set up this test. A separate doctest # remove-translations-by.txt tests a realistic run of the # remove-translations-by.py script under the actual rosettaadmin # db user. switch_dbuser('postgres') # Set up a template with Dutch and German translations. The # messages we set up here are invariant; they remain untouched # by deletions done in the test case. self.factory = LaunchpadObjectFactory() self.nl_pofile = self.factory.makePOFile('nl') self.potemplate = self.nl_pofile.potemplate self.de_pofile = self.factory.makePOFile('de', potemplate=self.potemplate) self.nl_message, self.de_message = self._makeMessages( "This message is not to be deleted.", "Dit bericht mag niet worden verwijderd.", "Diese Nachricht soll nicht erloescht werden.") self.untranslated_message = self.factory.makePOTMsgSet( self.potemplate, 'This message is untranslated.', sequence=0) self._checkInvariant() def _setTranslation(self, potmsgset, pofile, text, submitter=None, is_current_upstream=False): """Set translation for potmsgset in pofile to text.""" if submitter is None: submitter = self.potemplate.owner return self.factory.makeCurrentTranslationMessage( pofile, potmsgset, translator=submitter, translations={0: text}, current_other=is_current_upstream) def _makeMessages(self, template_text, nl_text, de_text, submitter=None, is_current_upstream=False): """Create message, and translate it to Dutch & German.""" message = self.factory.makePOTMsgSet(self.potemplate, template_text, sequence=0) new_nl_message = self._setTranslation( message, self.nl_pofile, nl_text, submitter=submitter, is_current_upstream=is_current_upstream) new_de_message = self._setTranslation( message, self.de_pofile, de_text, submitter=submitter, is_current_upstream=is_current_upstream) return new_nl_message, new_de_message def _getContents(self, pofile): return sorted(message.msgstr0.translation for message in pofile.translation_messages if message.msgstr0 is not None) def _checkInvariant(self): """Check that our translations are in their original state. Tests in this test case don't work in the usual way, by making changes and then testing for them. Instead they make changes by creating new messages, and then using `remove_translations` to undo those changes. We see that a removal worked correctly by verifying that the invariant is restored. """ # First make sure we're not reading out of cache. Store.of(self.nl_pofile).flush() self.assertEqual(self._getContents(self.nl_pofile), ["Dit bericht mag niet worden verwijderd."]) self.assertEqual(self._getContents(self.de_pofile), ["Diese Nachricht soll nicht erloescht werden."]) def _removeMessages(self, **kwargs): """Front-end for `remove_translations`. Flushes changes first.""" Store.of(self.potemplate).flush() return remove_translations(**kwargs) def test_RemoveNone(self): # If no messages match the given constraints, nothing is # deleted. rowcount = self._removeMessages(submitter=1, ids=[self.de_message.id], language_code='br') self.assertEqual(rowcount, 0) self._checkInvariant() def test_RemoveById(self): # We can remove messages by id. Other messages are not # affected. new_nl_message1 = self._setTranslation(self.untranslated_message, self.nl_pofile, "A Dutch translation") new_nl_message2 = self._setTranslation(self.untranslated_message, self.nl_pofile, "Double Dutch") self.assertEqual(self._getContents(self.nl_pofile), [ "A Dutch translation", "Dit bericht mag niet worden verwijderd.", "Double Dutch", ]) rowcount = self._removeMessages( ids=[new_nl_message1.id, new_nl_message2.id]) self.assertEqual(rowcount, 2) self._checkInvariant() def test_RemoveBySubmitter(self): # Remove messages by submitter id. carlos = getUtility(IPersonSet).getByName('carlos') (new_nl_message, new_de_message) = self._makeMessages("Submitted by Carlos", "Ingevoerd door Carlos", "Von Carlos eingefuehrt", submitter=carlos) # Ensure that at least one message's reviewer is not the same # as the submitter, so we know we're not accidentally matching # on reviewer instead. new_nl_message.reviewer = self.potemplate.owner self._removeMessages(submitter=carlos) self._checkInvariant() def test_RemoveByReviewer(self): # Remove messages by reviewer id. carlos = getUtility(IPersonSet).getByName('carlos') (new_nl_message, new_de_message) = self._makeMessages("Submitted by Carlos", "Ingevoerd door Carlos", "Von Carlos eingefuehrt") new_nl_message.reviewer = carlos new_de_message.reviewer = carlos self._removeMessages(reviewer=carlos) self._checkInvariant() def test_RemoveByTemplate(self): # Remove messages by template. Limit this deletion by ids as # well to avoid breaking the test invariant. To show that the # template limitation really does add a limit on top of the ids # themselves, we also pass the id of another message in a # different template. That message is not deleted. (new_nl_message, new_de_message) = self._makeMessages("Foo", "Foe", "Fu") unrelated_nl_pofile = self.factory.makePOFile('nl') potmsgset = self.factory.makePOTMsgSet(unrelated_nl_pofile.potemplate, 'Foo', sequence=0) unrelated_nl_message = self.factory.makeCurrentTranslationMessage( unrelated_nl_pofile, potmsgset, translator=unrelated_nl_pofile.potemplate.owner, translations={0: "Foe"}) ids = [new_nl_message.id, new_de_message.id, unrelated_nl_message.id] self._removeMessages(ids=ids, potemplate=self.potemplate.id) self._checkInvariant() self.assertEqual(self._getContents(unrelated_nl_pofile), ["Foe"]) def test_RemoveByLanguage(self): # Remove messages by language. Pass the ids of one Dutch # message and one German message, but specify Dutch as the # language to delete from; only the Dutch message is deleted. potmsgset = self.factory.makePOTMsgSet(self.potemplate, 'Bar', sequence=0) message = self._setTranslation(potmsgset, self.nl_pofile, 'Cafe') self._removeMessages(ids=[message.id, self.de_message.id], language_code='nl') self._checkInvariant() def test_RemoveByNotLanguage(self): # Remove messages, but spare otherwise matching messages that # are in German. potmsgset = self.factory.makePOTMsgSet(self.potemplate, 'Hi', sequence=0) message = self._setTranslation(potmsgset, self.nl_pofile, 'Hoi') self._removeMessages(ids=[message.id, self.de_message.id], language_code='de', not_language=True) self._checkInvariant() def test_RemoveCurrent(self): # Remove current messages, but not non-current messages. (new_nl_message, new_de_message) = self._makeMessages("translate", "vertalen", "uebersetzen") self.nl_message.is_current_upstream = False messages_to_delete = [self.nl_message, new_nl_message, new_de_message] current_upstream_messages = [ message for message in messages_to_delete if message.is_current_upstream ] ids = [message.id for message in messages_to_delete] logger = logging.getLogger('test_remove_translations') logger.setLevel(logging.WARN) loghandler = Handler(self) loghandler.add(logger.name) self._removeMessages(ids=ids, is_current_upstream=True, logger=logger) self.nl_message.is_current_upstream = True self._checkInvariant() loghandler.assertLogsMessage('Deleting messages currently in use:', level=logging.WARN) for message in current_upstream_messages: loghandler.assertLogsMessage( 'Message %i is a current translation in upstream' % message.id, level=logging.WARN) self.assertEqual(1 + len(current_upstream_messages), len(loghandler.records)) def test_RemoveNotCurrent(self): # Remove current messages, but not non-current messages. (new_nl_message, new_de_message) = self._makeMessages("write", "schrijven", "schreiben") new_nl_message.is_current_upstream = False new_de_message.is_current_upstream = False ids = [self.nl_message.id, new_nl_message.id, new_de_message.id] self._removeMessages(ids=ids, is_current_upstream=False) self._checkInvariant() def test_RemoveImported(self): # Remove current messages, but not non-current messages. (new_nl_message, new_de_message) = self._makeMessages("book", "boek", "Buch") new_nl_message.is_current_ubuntu = True new_de_message.is_current_ubuntu = True ids = [self.nl_message.id, new_nl_message.id, new_de_message.id] self._removeMessages(ids=ids, is_current_ubuntu=True) self._checkInvariant() def test_RemoveNotImported(self): # Remove current messages, but not non-current messages. (new_nl_message, new_de_message) = self._makeMessages("helicopter", "helikopter", "Hubschauber") self.nl_message.is_current_ubuntu = True ids = [self.nl_message.id, new_nl_message.id, new_de_message.id] self._removeMessages(ids=ids, is_current_ubuntu=False) self.nl_message.is_current_ubuntu = False self._checkInvariant() def test_RemoveMsgId(self): # Remove translations by msgid_singular. (new_nl_message, new_de_message) = self._makeMessages("save", "bewaren", "speichern") self._removeMessages(msgid_singular="save") self._checkInvariant() def test_RemoveOrigin(self): # Remove translations by origin. self.assertEqual(self.nl_message.origin, RosettaTranslationOrigin.ROSETTAWEB) (new_nl_message, new_de_message) = self._makeMessages("new", "nieuw", "neu", is_current_upstream=True) removeSecurityProxy(new_nl_message).origin = ( RosettaTranslationOrigin.SCM) removeSecurityProxy(new_de_message).origin = ( RosettaTranslationOrigin.SCM) self._removeMessages(potemplate=self.potemplate, origin=RosettaTranslationOrigin.SCM) self._checkInvariant() def test_remove_by_license_rejection(self): # Remove translations submitted by users who rejected the # licensing agreement. refusenik = self.factory.makePerson() self._makeMessages("Don't download this song", "Niet delen", "Nicht teilen", submitter=refusenik) TranslationRelicensingAgreement(person=refusenik, allow_relicensing=False) self._removeMessages(reject_license=True) self._checkInvariant() def test_remove_unlicensed_none(self): # Removing translations whose submitters rejected our # translations licence does not affect translations by those who # haven't answered the question yet. self._removeMessages(reject_license=True) self._checkInvariant() def test_remove_unlicensed_when_licensed(self): # Removing translations whose submitters rejected our # translations licence does not affect translations by those who # agreed to license. answer = TranslationRelicensingAgreement( person=self.nl_message.submitter, allow_relicensing=True) try: self._removeMessages(reject_license=True) self._checkInvariant() finally: # Clean up. answer.destroySelf() def test_remove_unlicensed_restriction(self): # When removing unlicensed translations, other restrictions # still apply. self.nl_message.is_current_upstream = True self.de_message.is_current_upstream = True answer = TranslationRelicensingAgreement( person=self.nl_message.submitter, allow_relicensing=False) try: self._removeMessages(reject_license=True, is_current_upstream=False) self._checkInvariant() finally: # Clean up. answer.destroySelf()
class TestRemoveTranslationsOptionsHandling(TestCase): """Test `RemoveTranslations`' options parsing and type checking.""" layer = LaunchpadZopelessLayer def setUp(self): super(TestRemoveTranslationsOptionsHandling, self).setUp() self.factory = LaunchpadObjectFactory() def test_WithNativeArgs(self): # Options can be passed as the string representations of the # types the script wants them in. options = parse_opts([ '--submitter=1', '--reviewer=2', '--id=3', '--id=4', '--potemplate=5', '--language=te', '--not-language', '--is-current-ubuntu=True', '--is-current-upstream=False', '--msgid=Hello', '--origin=1', '--force', ]) self.assertThat( options, MatchesStructure.byEquality(submitter=1, reviewer=2, ids=[3, 4], potemplate=5, language='te', not_language=True, is_current_ubuntu=True, is_current_upstream=False, origin=1, force=True)) def test_WithLookups(self): # The script can also look up some items from different # representations: person names, numbers or different case # settings for booleans, and translation origin identifiers. submitter = self.factory.makePerson() reviewer = self.factory.makePerson() options = parse_opts([ '--submitter=%s' % submitter.name, '--reviewer=%s' % reviewer.name, '--is-current-ubuntu=0', '--is-current-upstream=true', '--origin=SCM', ]) self.assertThat( options, MatchesStructure.byEquality( submitter=submitter.id, reviewer=reviewer.id, is_current_ubuntu=False, is_current_upstream=True, origin=RosettaTranslationOrigin.SCM.value)) def test_BadBool(self): self.assertRaises(Exception, parse_opts, '--is-current-ubuntu=None') def test_UnknownPerson(self): self.assertRaises(Exception, parse_opts, '--reviewer=unknownnonexistentpersonbird') def test_UnknownOrigin(self): self.assertRaises(Exception, parse_opts, '--origin=GAGA')
class TestBranchMergeProposalDelta(TestCase): layer = LaunchpadFunctionalLayer def setUp(self): TestCase.setUp(self) login('*****@*****.**') self.factory = LaunchpadObjectFactory() def test_snapshot(self): """Test that the snapshot method produces a reasonable snapshot""" merge_proposal = self.factory.makeBranchMergeProposal() merge_proposal.commit_message = 'foo' merge_proposal.whiteboard = 'bar' snapshot = BranchMergeProposalDelta.snapshot(merge_proposal) self.assertEqual('foo', snapshot.commit_message) self.assertEqual('bar', snapshot.whiteboard) def test_noModification(self): """When there are no modifications, no delta should be returned.""" merge_proposal = self.factory.makeBranchMergeProposal() old_merge_proposal = BranchMergeProposalDelta.snapshot(merge_proposal) delta = BranchMergeProposalDelta.construct(old_merge_proposal, merge_proposal) assert delta is None def test_Modification(self): """When there are modifications, the delta reflects them.""" registrant = self.factory.makePerson(displayname='Baz Qux', email='*****@*****.**') merge_proposal = self.factory.makeBranchMergeProposal( registrant=registrant) old_merge_proposal = BranchMergeProposalDelta.snapshot(merge_proposal) merge_proposal.commit_message = 'Change foo into bar.' merge_proposal.description = 'Set the description.' merge_proposal.markAsMerged() delta = BranchMergeProposalDelta.construct(old_merge_proposal, merge_proposal) assert delta is not None self.assertEqual('Change foo into bar.', delta.commit_message) self.assertEqual('Set the description.', delta.description) self.assertEqual( { 'old': BranchMergeProposalStatus.WORK_IN_PROGRESS, 'new': BranchMergeProposalStatus.MERGED }, delta.queue_status) def test_monitor(self): """\ `monitor` observes changes to a given merge proposal and issues `ObjectModifiedEvent` events if there are any. """ merge_proposal = self.factory.makeBranchMergeProposal() with EventRecorder() as event_recorder: # No event is issued when nothing is changed. with BranchMergeProposalDelta.monitor(merge_proposal): pass # Don't make changes. self.assertEqual(0, len(event_recorder.events)) # When one or more properties (of interest to # BranchMergeProposalDelta) are changed, a single event is issued. with BranchMergeProposalDelta.monitor(merge_proposal): merge_proposal.commit_message = "foo" merge_proposal.whiteboard = "bar" self.assertEqual(1, len(event_recorder.events)) [event] = event_recorder.events self.assertIsInstance(event, ObjectModifiedEvent) self.assertEqual(merge_proposal, event.object) self.assertContentEqual(["commit_message", "whiteboard"], event.edited_fields)
def _makeHarry(self, email_address_status=None): factory = LaunchpadObjectFactory() return factory.makePerson( email='*****@*****.**', name='harry', email_address_status=email_address_status)
def _makeHarry(self, email_address_status=None): factory = LaunchpadObjectFactory() return factory.makePerson(email='*****@*****.**', name='harry', email_address_status=email_address_status)
class TestWorkerMonitorIntegration(TestCaseInTempDir, TestCase): layer = ZopelessAppServerLayer run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=60) def setUp(self): super(TestWorkerMonitorIntegration, self).setUp() login('*****@*****.**') self.factory = LaunchpadObjectFactory() nuke_codeimport_sample_data() self.repo_path = tempfile.mkdtemp() self.disable_directory_isolation() self.addCleanup(shutil.rmtree, self.repo_path) self.foreign_commit_count = 0 def tearDown(self): super(TestWorkerMonitorIntegration, self).tearDown() logout() def makeCVSCodeImport(self): """Make a `CodeImport` that points to a real CVS repository.""" cvs_server = CVSServer(self.repo_path) cvs_server.start_server() self.addCleanup(cvs_server.stop_server) cvs_server.makeModule('trunk', [('README', 'original\n')]) self.foreign_commit_count = 2 return self.factory.makeCodeImport(cvs_root=cvs_server.getRoot(), cvs_module='trunk') def makeSVNCodeImport(self): """Make a `CodeImport` that points to a real Subversion repository.""" self.subversion_server = SubversionServer(self.repo_path) self.subversion_server.start_server() self.addCleanup(self.subversion_server.stop_server) url = self.subversion_server.makeBranch('trunk', [('README', 'contents')]) self.foreign_commit_count = 2 return self.factory.makeCodeImport(svn_branch_url=url) def makeBzrSvnCodeImport(self): """Make a `CodeImport` that points to a real Subversion repository.""" self.subversion_server = SubversionServer(self.repo_path, use_svn_serve=True) self.subversion_server.start_server() self.addCleanup(self.subversion_server.stop_server) url = self.subversion_server.makeBranch('trunk', [('README', 'contents')]) self.foreign_commit_count = 2 return self.factory.makeCodeImport( svn_branch_url=url, rcs_type=RevisionControlSystems.BZR_SVN) def makeGitCodeImport(self, target_rcs_type=None): """Make a `CodeImport` that points to a real Git repository.""" self.git_server = GitServer(self.repo_path, use_server=False) self.git_server.start_server() self.addCleanup(self.git_server.stop_server) self.git_server.makeRepo('source', [('README', 'contents')]) self.foreign_commit_count = 1 return self.factory.makeCodeImport( git_repo_url=self.git_server.get_url('source'), target_rcs_type=target_rcs_type) def makeBzrCodeImport(self): """Make a `CodeImport` that points to a real Bazaar branch.""" self.bzr_server = BzrServer(self.repo_path) self.bzr_server.start_server() self.addCleanup(self.bzr_server.stop_server) self.bzr_server.makeRepo([('README', 'contents')]) self.foreign_commit_count = 1 return self.factory.makeCodeImport( bzr_branch_url=self.bzr_server.get_url()) def getStartedJobForImport(self, code_import): """Get a started `CodeImportJob` for `code_import`. This method approves the import, creates a job, marks it started and returns the job. It also makes sure there are no branches or foreign trees in the default stores to interfere with processing this job. """ if code_import.review_status != CodeImportReviewStatus.REVIEWED: code_import.updateFromData( {'review_status': CodeImportReviewStatus.REVIEWED}, self.factory.makePerson()) job = getUtility(ICodeImportJobSet).getJobForMachine('machine', 10) self.assertEqual(code_import, job.code_import) source_details = CodeImportSourceDetails.fromArguments( removeSecurityProxy(job.makeWorkerArguments())) if IBranch.providedBy(code_import.target): clean_up_default_stores_for_import(source_details) self.addCleanup(clean_up_default_stores_for_import, source_details) return job def makeTargetGitServer(self): """Set up a target Git server that can receive imports.""" self.target_store = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.target_store) self.target_git_server = GitServer(self.target_store, use_server=True) self.target_git_server.start_server() self.addCleanup(self.target_git_server.stop_server) config_name = self.getUniqueString() config_fixture = self.useFixture( ConfigFixture(config_name, self.layer.config_fixture.instance_name)) setting_lines = [ "[codehosting]", "git_browse_root: %s" % self.target_git_server.get_url(""), "", "[launchpad]", "internal_macaroon_secret_key: some-secret", ] config_fixture.add_section("\n" + "\n".join(setting_lines)) self.useFixture(ConfigUseFixture(config_name)) self.useFixture(GitHostingFixture()) def assertCodeImportResultCreated(self, code_import): """Assert that a `CodeImportResult` was created for `code_import`.""" self.assertEqual(len(list(code_import.results)), 1) result = list(code_import.results)[0] self.assertEqual(result.status, CodeImportResultStatus.SUCCESS) def assertBranchImportedOKForCodeImport(self, code_import): """Assert that a branch was pushed into the default branch store.""" if IBranch.providedBy(code_import.target): url = get_default_bazaar_branch_store()._getMirrorURL( code_import.branch.id) branch = Branch.open(url) commit_count = branch.revno() else: repo_path = os.path.join(self.target_store, code_import.target.unique_name) commit_count = int( subprocess.check_output(["git", "rev-list", "--count", "HEAD"], cwd=repo_path, universal_newlines=True)) self.assertEqual(self.foreign_commit_count, commit_count) def assertImported(self, code_import_id): """Assert that the `CodeImport` of the given id was imported.""" # In the in-memory tests, check that resetTimeout on the # CodeImportWorkerMonitorProtocol was called at least once. if self._protocol is not None: self.assertPositive(self._protocol.reset_calls) code_import = getUtility(ICodeImportSet).get(code_import_id) self.assertCodeImportResultCreated(code_import) self.assertBranchImportedOKForCodeImport(code_import) def performImport(self, job_id): """Perform the import job with ID job_id. Return a Deferred that fires when it the job is done. This implementation does it in-process. """ logger = BufferLogger() monitor = CIWorkerMonitorForTesting( job_id, logger, xmlrpc.Proxy(config.codeimportdispatcher.codeimportscheduler_url), "anything") deferred = monitor.run() def save_protocol_object(result): """Save the process protocol object. We do this in an addBoth so that it's called after the process protocol is actually constructed but before we drop the last reference to the monitor object. """ self._protocol = monitor._protocol return result return deferred.addBoth(save_protocol_object) @defer.inlineCallbacks def test_import_cvs(self): # Create a CVS CodeImport and import it. job = self.getStartedJobForImport(self.makeCVSCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() yield self.performImport(job_id) self.assertImported(code_import_id) @defer.inlineCallbacks def test_import_subversion(self): # Create a Subversion CodeImport and import it. job = self.getStartedJobForImport(self.makeSVNCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() yield self.performImport(job_id) self.assertImported(code_import_id) @defer.inlineCallbacks def test_import_git(self): # Create a Git CodeImport and import it. job = self.getStartedJobForImport(self.makeGitCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() yield self.performImport(job_id) self.assertImported(code_import_id) @defer.inlineCallbacks def test_import_git_to_git(self): # Create a Git-to-Git CodeImport and import it. self.makeTargetGitServer() job = self.getStartedJobForImport( self.makeGitCodeImport( target_rcs_type=TargetRevisionControlSystems.GIT)) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() target_repo_path = os.path.join(self.target_store, job.code_import.target.unique_name) os.makedirs(target_repo_path) self.target_git_server.createRepository(target_repo_path, bare=True) yield self.performImport(job_id) self.assertImported(code_import_id) target_repo = GitRepo(target_repo_path) self.assertContentEqual(["heads/master"], target_repo.refs.keys(base="refs")) self.assertEqual("ref: refs/heads/master", target_repo.refs.read_ref("HEAD")) @defer.inlineCallbacks def test_import_git_to_git_refs_changed(self): # Create a Git-to-Git CodeImport and import it incrementally with # ref and HEAD changes. self.makeTargetGitServer() job = self.getStartedJobForImport( self.makeGitCodeImport( target_rcs_type=TargetRevisionControlSystems.GIT)) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() source_repo = GitRepo(os.path.join(self.repo_path, "source")) commit = source_repo.refs["refs/heads/master"] source_repo.refs["refs/heads/one"] = commit source_repo.refs["refs/heads/two"] = commit source_repo.refs.set_symbolic_ref("HEAD", "refs/heads/one") del source_repo.refs["refs/heads/master"] target_repo_path = os.path.join(self.target_store, job.code_import.target.unique_name) self.target_git_server.makeRepo(job.code_import.target.unique_name, [("NEWS", "contents")]) yield self.performImport(job_id) self.assertImported(code_import_id) target_repo = GitRepo(target_repo_path) self.assertContentEqual(["heads/one", "heads/two"], target_repo.refs.keys(base="refs")) self.assertEqual("ref: refs/heads/one", GitRepo(target_repo_path).refs.read_ref("HEAD")) @defer.inlineCallbacks def test_import_bzr(self): # Create a Bazaar CodeImport and import it. job = self.getStartedJobForImport(self.makeBzrCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() yield self.performImport(job_id) self.assertImported(code_import_id) @defer.inlineCallbacks def test_import_bzrsvn(self): # Create a Subversion-via-bzr-svn CodeImport and import it. job = self.getStartedJobForImport(self.makeBzrSvnCodeImport()) code_import_id = job.code_import.id job_id = job.id self.layer.txn.commit() yield self.performImport(job_id) self.assertImported(code_import_id)
class TestBranchMergeProposalDelta(TestCase): layer = LaunchpadFunctionalLayer def setUp(self): TestCase.setUp(self) login('*****@*****.**') self.factory = LaunchpadObjectFactory() def test_snapshot(self): """Test that the snapshot method produces a reasonable snapshot""" merge_proposal = self.factory.makeBranchMergeProposal() merge_proposal.commit_message = 'foo' merge_proposal.whiteboard = 'bar' snapshot = BranchMergeProposalDelta.snapshot(merge_proposal) self.assertEqual('foo', snapshot.commit_message) self.assertEqual('bar', snapshot.whiteboard) def test_noModification(self): """When there are no modifications, no delta should be returned.""" merge_proposal = self.factory.makeBranchMergeProposal() old_merge_proposal = BranchMergeProposalDelta.snapshot(merge_proposal) delta = BranchMergeProposalDelta.construct( old_merge_proposal, merge_proposal) assert delta is None def test_Modification(self): """When there are modifications, the delta reflects them.""" registrant = self.factory.makePerson( displayname='Baz Qux', email='*****@*****.**') merge_proposal = self.factory.makeBranchMergeProposal( registrant=registrant) old_merge_proposal = BranchMergeProposalDelta.snapshot(merge_proposal) merge_proposal.commit_message = 'Change foo into bar.' merge_proposal.description = 'Set the description.' merge_proposal.markAsMerged() delta = BranchMergeProposalDelta.construct( old_merge_proposal, merge_proposal) assert delta is not None self.assertEqual('Change foo into bar.', delta.commit_message) self.assertEqual('Set the description.', delta.description) self.assertEqual( {'old': BranchMergeProposalStatus.WORK_IN_PROGRESS, 'new': BranchMergeProposalStatus.MERGED}, delta.queue_status) def test_monitor(self): """\ `monitor` observes changes to a given merge proposal and issues `ObjectModifiedEvent` events if there are any. """ merge_proposal = self.factory.makeBranchMergeProposal() with EventRecorder() as event_recorder: # No event is issued when nothing is changed. with BranchMergeProposalDelta.monitor(merge_proposal): pass # Don't make changes. self.assertEqual(0, len(event_recorder.events)) # When one or more properties (of interest to # BranchMergeProposalDelta) are changed, a single event is issued. with BranchMergeProposalDelta.monitor(merge_proposal): merge_proposal.commit_message = "foo" merge_proposal.whiteboard = "bar" self.assertEqual(1, len(event_recorder.events)) [event] = event_recorder.events self.assertIsInstance(event, ObjectModifiedEvent) self.assertEqual(merge_proposal, event.object) self.assertContentEqual( ["commit_message", "whiteboard"], event.edited_fields)
class TestGetGuessedPOFile(TestCaseWithFactory): """Test matching of PO files with respective templates and languages.""" layer = LaunchpadZopelessLayer def setUp(self): """Set up context to test in.""" super(TestGetGuessedPOFile, self).setUp() self.queue = getUtility(ITranslationImportQueue) self.factory = LaunchpadObjectFactory() self.distribution = self.factory.makeDistribution('boohoo') self.distroseries = self.factory.makeDistroSeries(self.distribution) self.uploaderperson = self.factory.makePerson() def createSourcePackageAndPOTemplate(self, sourcepackagename, template): """Create and return a source package and a POTemplate. Creates a source package in the self.distroseries with the passed-in sourcepackagename, and a template in that sourcepackage named template with the identical translation domain. """ target_sourcepackage = self.factory.makeSourcePackage( distroseries=self.distroseries) pot = self.factory.makePOTemplate( sourcepackagename=target_sourcepackage.sourcepackagename, distroseries=target_sourcepackage.distroseries, name=template, translation_domain=template) spn = self.factory.makeSourcePackageName(sourcepackagename) l10n_sourcepackage = self.factory.makeSourcePackage( sourcepackagename=spn, distroseries=self.distroseries) return (l10n_sourcepackage, pot) def _getGuessedPOFile(self, source_name, template_path): """Return new POTemplate and matched POFile for package and template. """ template_name = os.path.basename(template_path) package, pot = self.createSourcePackageAndPOTemplate( source_name, template_name) queue_entry = self.queue.addOrUpdateEntry( '%s.po' % template_path, template_name, True, self.uploaderperson, distroseries=package.distroseries, sourcepackagename=package.sourcepackagename) pofile = queue_entry.getGuessedPOFile() return (pot, pofile) def test_KDE4_language(self): # PO files 'something.po' in a package named like 'kde-l10n-sr' # belong in the 'something' translation domain as Serbian (sr) # translations. potemplate, pofile = self._getGuessedPOFile('kde-l10n-sr', 'template') serbian = getUtility(ILanguageSet).getLanguageByCode('sr') self.assertEqual(potemplate, pofile.potemplate) self.assertEqual(serbian, pofile.language) def test_KDE4_language_country(self): # If package name is kde-l10n-engb, it needs to be mapped # to British English (en_GB). potemplate, pofile = self._getGuessedPOFile('kde-l10n-engb', 'template') real_english = getUtility(ILanguageSet).getLanguageByCode('en_GB') self.assertEqual(potemplate, pofile.potemplate) self.assertEqual(real_english, pofile.language) def test_KDE4_language_variant(self): # If package name is kde-l10n-ca-valencia, it needs to be mapped # to Valencian variant of Catalan (ca@valencia). catalan_valencia = self.factory.makeLanguage('ca@valencia', 'Catalan Valencia') potemplate, pofile = self._getGuessedPOFile('kde-l10n-ca-valencia', 'template') self.assertEqual(potemplate, pofile.potemplate) self.assertEqual(catalan_valencia, pofile.language) def test_KDE4_language_subvariant(self): # PO file 'sr@test/something.po' in a package named like # 'kde-l10n-sr' belong in the 'something' translation domain # for "sr@test" language translations. serbian_test = self.factory.makeLanguage('sr@test') potemplate, pofile = self._getGuessedPOFile('kde-l10n-sr', 'sr@test/template') self.assertEqual(potemplate, pofile.potemplate) self.assertEqual(serbian_test, pofile.language) def test_KDE4_language_at_sign(self): # PO file 'blah@test/something.po' in a package named like # 'kde-l10n-sr' belong in the 'something' translation domain # for "sr" language translations. serbian = getUtility(ILanguageSet).getLanguageByCode('sr') potemplate, pofile = self._getGuessedPOFile( 'kde-l10n-sr', 'source/blah@test/template') self.assertEqual(potemplate, pofile.potemplate) self.assertEqual(serbian, pofile.language)
class TestBranchPopupWidget(unittest.TestCase): """Tests for the branch popup widget.""" layer = LaunchpadFunctionalLayer def assertIs(self, first, second): """Assert `first` is `second`.""" self.assertTrue(first is second, "%r is not %r" % (first, second)) def installLaunchBag(self, user=None, product=None): bag = DummyLaunchBag(user, product) provideUtility(bag, ILaunchBag) return bag def makeBranchPopup(self, vocabulary=None): # Pick a random, semi-appropriate context. context = self.factory.makeProduct() if vocabulary is None: vocabulary = BranchVocabulary(context) request = self.makeRequest() return BranchPopupWidget(self.makeField(context, vocabulary), vocabulary, request) def makeField(self, context, vocabulary): field = Choice(title=_('Branch'), vocabulary=vocabulary, required=False, description=_("The Bazaar branch.")) field.context = context return field def makeRequest(self): return LaunchpadTestRequest() def setUp(self): login(ANONYMOUS) self._original_launch_bag = getUtility(ILaunchBag) self.factory = LaunchpadObjectFactory() self.launch_bag = self.installLaunchBag( user=self.factory.makePerson(), product=self.factory.makeProduct()) self.popup = self.makeBranchPopup() def tearDown(self): provideUtility(self._original_launch_bag, ILaunchBag) logout() def test_getProduct(self): """getProduct() returns the product in the LaunchBag.""" self.assertEqual(self.launch_bag.product, self.popup.getProduct()) def test_getPerson(self): """getPerson() returns the logged-in user.""" self.assertEqual(self.launch_bag.user, self.popup.getPerson()) def test_getBranchNameFromURL(self): """getBranchNameFromURL() gets a branch name from a url. In general, the name is the last path segment of the URL. """ url = self.factory.getUniqueURL() name = self.popup.getBranchNameFromURL(url) self.assertEqual(URI(url).path.split('/')[-1], name) def test_makeBranchFromURL(self): """makeBranchFromURL(url) creates a mirrored branch at `url`. The owner and registrant are the currently logged-in user, as given by getPerson(), and the product is the product in the LaunchBag. """ url = self.factory.getUniqueURL() expected_name = self.popup.getBranchNameFromURL(url) branch = self.popup.makeBranchFromURL(url) self.assertEqual(BranchType.MIRRORED, branch.branch_type) self.assertEqual(url, branch.url) self.assertEqual(self.popup.getPerson(), branch.owner) self.assertEqual(self.popup.getPerson(), branch.registrant) self.assertEqual(self.popup.getProduct(), branch.product) self.assertEqual(expected_name, branch.name) def test_makeBranch_used(self): # makeBranch makes up the branch name if the inferred one is already # used. url = self.factory.getUniqueURL() expected_name = self.popup.getBranchNameFromURL(url) self.factory.makeProductBranch(name=expected_name, product=self.popup.getProduct(), owner=self.popup.getPerson()) branch = self.popup.makeBranchFromURL(url) self.assertEqual(expected_name + '-1', branch.name) def test_makeBranchRequestsMirror(self): """makeBranch requests a mirror on the branch it creates.""" url = self.factory.getUniqueURL() branch = self.popup.makeBranchFromURL(url) self.assertNotEqual('None', str(branch.next_mirror_time)) def test_makeBranchNoProduct(self): """makeBranchFromURL(url) returns None if there's no product. Not all contexts for branch registration have products. In particular, a bug can be on a source package. When we link a branch to that bug, there's no clear product to choose, so we don't choose any. """ self.installLaunchBag(product=None, user=self.factory.makePerson()) url = self.factory.getUniqueURL() self.assertRaises(NoProductError, self.popup.makeBranchFromURL, url) def test_makeBranchTrailingSlash(self): """makeBranch creates a mirrored branch even if the URL ends with /. """ uri = URI(self.factory.getUniqueURL()) expected_name = self.popup.getBranchNameFromURL( str(uri.ensureNoSlash())) branch = self.popup.makeBranchFromURL(str(uri.ensureSlash())) self.assertEqual(str(uri.ensureNoSlash()), branch.url) self.assertEqual(expected_name, branch.name) def test_toFieldValueFallsBackToMakingBranch(self): """_toFieldValue falls back to making a branch if it's given a URL.""" url = self.factory.getUniqueURL() # Check that there's no branch with this URL. self.assertIs(None, getUtility(IBranchLookup).getByUrl(url)) branch = self.popup._toFieldValue(url) self.assertEqual(url, branch.url) def test_toFieldValueFetchesTheExistingBranch(self): """_toFieldValue returns the existing branch that has that URL.""" expected_branch = self.factory.makeAnyBranch( branch_type=BranchType.MIRRORED) branch = self.popup._toFieldValue(expected_branch.url) self.assertEqual(expected_branch, branch) def test_toFieldValueNonURL(self): """When the input isn't a URL, fall back to the original error.""" empty_search = 'doesntexist' self.assertRaises(ConversionError, self.popup._toFieldValue, empty_search) def test_toFieldValueNoProduct(self): """When there's no product, fall back to the original error.""" self.installLaunchBag(product=None, user=self.factory.makePerson()) self.assertRaises(ConversionError, self.popup._toFieldValue, self.factory.getUniqueURL()) def test_toFieldBadURL(self): """When the input is a bad URL, fall back to the original error. There are many valid URLs that are inappropriate for a mirrored branch. We don't want to register a mirrored branch when someone enters such a URL. """ bad_url = 'svn://svn.example.com/repo/trunk' self.assertRaises(ConversionError, self.popup._toFieldValue, bad_url) def test_branchInRestrictedProduct(self): # There are two reasons for a URL not being in the vocabulary. One # reason is that it's there's no registered branch with that URL. The # other is that the vocabulary on this form is restricted to one # product, and there *is* a branch with that URL, but it's registered # on a different product. # Make a popup restricted to a particular product. vocab = BranchRestrictedOnProductVocabulary(self.launch_bag.product) self.assertEqual(vocab.product, self.launch_bag.product) popup = self.makeBranchPopup(vocab) # Make a branch on a different product. branch = self.factory.makeProductBranch( branch_type=BranchType.MIRRORED) self.assertNotEqual(self.launch_bag.product, branch.product) # Trying to make a branch with that URL will fail. self.assertRaises(ConversionError, popup._toFieldValue, branch.url)
class TestGetGuessedPOFile(TestCaseWithFactory): """Test matching of PO files with respective templates and languages.""" layer = LaunchpadZopelessLayer def setUp(self): """Set up context to test in.""" super(TestGetGuessedPOFile, self).setUp() self.queue = getUtility(ITranslationImportQueue) self.factory = LaunchpadObjectFactory() self.distribution = self.factory.makeDistribution('boohoo') self.distroseries = self.factory.makeDistroSeries(self.distribution) self.uploaderperson = self.factory.makePerson() def createSourcePackageAndPOTemplate(self, sourcepackagename, template): """Create and return a source package and a POTemplate. Creates a source package in the self.distroseries with the passed-in sourcepackagename, and a template in that sourcepackage named template with the identical translation domain. """ target_sourcepackage = self.factory.makeSourcePackage( distroseries=self.distroseries) pot = self.factory.makePOTemplate( sourcepackagename=target_sourcepackage.sourcepackagename, distroseries=target_sourcepackage.distroseries, name=template, translation_domain=template) spn = self.factory.makeSourcePackageName(sourcepackagename) l10n_sourcepackage = self.factory.makeSourcePackage( sourcepackagename=spn, distroseries=self.distroseries) return (l10n_sourcepackage, pot) def _getGuessedPOFile(self, source_name, template_path): """Return new POTemplate and matched POFile for package and template. """ template_name = os.path.basename(template_path) package, pot = self.createSourcePackageAndPOTemplate( source_name, template_name) queue_entry = self.queue.addOrUpdateEntry( '%s.po' % template_path, template_name, True, self.uploaderperson, distroseries=package.distroseries, sourcepackagename=package.sourcepackagename) pofile = queue_entry.getGuessedPOFile() return (pot, pofile) def test_KDE4_language(self): # PO files 'something.po' in a package named like 'kde-l10n-sr' # belong in the 'something' translation domain as Serbian (sr) # translations. potemplate, pofile = self._getGuessedPOFile( 'kde-l10n-sr', 'template') serbian = getUtility(ILanguageSet).getLanguageByCode('sr') self.assertEquals(potemplate, pofile.potemplate) self.assertEquals(serbian, pofile.language) def test_KDE4_language_country(self): # If package name is kde-l10n-engb, it needs to be mapped # to British English (en_GB). potemplate, pofile = self._getGuessedPOFile( 'kde-l10n-engb', 'template') real_english = getUtility(ILanguageSet).getLanguageByCode('en_GB') self.assertEquals(potemplate, pofile.potemplate) self.assertEquals(real_english, pofile.language) def test_KDE4_language_variant(self): # If package name is kde-l10n-ca-valencia, it needs to be mapped # to Valencian variant of Catalan (ca@valencia). catalan_valencia = self.factory.makeLanguage( 'ca@valencia', 'Catalan Valencia') potemplate, pofile = self._getGuessedPOFile( 'kde-l10n-ca-valencia', 'template') self.assertEquals(potemplate, pofile.potemplate) self.assertEquals(catalan_valencia, pofile.language) def test_KDE4_language_subvariant(self): # PO file 'sr@test/something.po' in a package named like # 'kde-l10n-sr' belong in the 'something' translation domain # for "sr@test" language translations. serbian_test = self.factory.makeLanguage('sr@test') potemplate, pofile = self._getGuessedPOFile( 'kde-l10n-sr', 'sr@test/template') self.assertEquals(potemplate, pofile.potemplate) self.assertEquals(serbian_test, pofile.language) def test_KDE4_language_at_sign(self): # PO file 'blah@test/something.po' in a package named like # 'kde-l10n-sr' belong in the 'something' translation domain # for "sr" language translations. serbian = getUtility(ILanguageSet).getLanguageByCode('sr') potemplate, pofile = self._getGuessedPOFile( 'kde-l10n-sr', 'source/blah@test/template') self.assertEquals(potemplate, pofile.potemplate) self.assertEquals(serbian, pofile.language)
class TestRemoveTranslations(TestCase): """Test `remove_translations`.""" layer = LaunchpadZopelessLayer def setUp(self): super(TestRemoveTranslations, self).setUp() # Acquire privileges to delete TranslationMessages. That's not # something we normally do. Actually we should test under # rosettaadmin, but that user does not have all the privileges # needed to set up this test. A separate doctest # remove-translations-by.txt tests a realistic run of the # remove-translations-by.py script under the actual rosettaadmin # db user. switch_dbuser('postgres') # Set up a template with Dutch and German translations. The # messages we set up here are invariant; they remain untouched # by deletions done in the test case. self.factory = LaunchpadObjectFactory() self.nl_pofile = self.factory.makePOFile('nl') self.potemplate = self.nl_pofile.potemplate self.de_pofile = self.factory.makePOFile( 'de', potemplate=self.potemplate) self.nl_message, self.de_message = self._makeMessages( "This message is not to be deleted.", "Dit bericht mag niet worden verwijderd.", "Diese Nachricht soll nicht erloescht werden.") self.untranslated_message = self.factory.makePOTMsgSet( self.potemplate, 'This message is untranslated.', sequence=0) self._checkInvariant() def _setTranslation(self, potmsgset, pofile, text, submitter=None, is_current_upstream=False): """Set translation for potmsgset in pofile to text.""" if submitter is None: submitter = self.potemplate.owner return self.factory.makeCurrentTranslationMessage( pofile, potmsgset, translator=submitter, translations={0: text}, current_other=is_current_upstream) def _makeMessages(self, template_text, nl_text, de_text, submitter=None, is_current_upstream=False): """Create message, and translate it to Dutch & German.""" message = self.factory.makePOTMsgSet(self.potemplate, template_text, sequence=0) new_nl_message = self._setTranslation( message, self.nl_pofile, nl_text, submitter=submitter, is_current_upstream=is_current_upstream) new_de_message = self._setTranslation( message, self.de_pofile, de_text, submitter=submitter, is_current_upstream=is_current_upstream) return new_nl_message, new_de_message def _getContents(self, pofile): return sorted( message.msgstr0.translation for message in pofile.translation_messages if message.msgstr0 is not None) def _checkInvariant(self): """Check that our translations are in their original state. Tests in this test case don't work in the usual way, by making changes and then testing for them. Instead they make changes by creating new messages, and then using `remove_translations` to undo those changes. We see that a removal worked correctly by verifying that the invariant is restored. """ # First make sure we're not reading out of cache. Store.of(self.nl_pofile).flush() self.assertEqual( self._getContents(self.nl_pofile), ["Dit bericht mag niet worden verwijderd."]) self.assertEqual( self._getContents(self.de_pofile), ["Diese Nachricht soll nicht erloescht werden."]) def _removeMessages(self, **kwargs): """Front-end for `remove_translations`. Flushes changes first.""" Store.of(self.potemplate).flush() return remove_translations(**kwargs) def test_RemoveNone(self): # If no messages match the given constraints, nothing is # deleted. rowcount = self._removeMessages( submitter=1, ids=[self.de_message.id], language_code='br') self.assertEqual(rowcount, 0) self._checkInvariant() def test_RemoveById(self): # We can remove messages by id. Other messages are not # affected. new_nl_message1 = self._setTranslation( self.untranslated_message, self.nl_pofile, "A Dutch translation") new_nl_message2 = self._setTranslation( self.untranslated_message, self.nl_pofile, "Double Dutch") self.assertEqual( self._getContents(self.nl_pofile), [ "A Dutch translation", "Dit bericht mag niet worden verwijderd.", "Double Dutch", ]) rowcount = self._removeMessages( ids=[new_nl_message1.id, new_nl_message2.id]) self.assertEqual(rowcount, 2) self._checkInvariant() def test_RemoveBySubmitter(self): # Remove messages by submitter id. carlos = getUtility(IPersonSet).getByName('carlos') (new_nl_message, new_de_message) = self._makeMessages( "Submitted by Carlos", "Ingevoerd door Carlos", "Von Carlos eingefuehrt", submitter=carlos) # Ensure that at least one message's reviewer is not the same # as the submitter, so we know we're not accidentally matching # on reviewer instead. new_nl_message.reviewer = self.potemplate.owner self._removeMessages(submitter=carlos) self._checkInvariant() def test_RemoveByReviewer(self): # Remove messages by reviewer id. carlos = getUtility(IPersonSet).getByName('carlos') (new_nl_message, new_de_message) = self._makeMessages( "Submitted by Carlos", "Ingevoerd door Carlos", "Von Carlos eingefuehrt") new_nl_message.reviewer = carlos new_de_message.reviewer = carlos self._removeMessages(reviewer=carlos) self._checkInvariant() def test_RemoveByTemplate(self): # Remove messages by template. Limit this deletion by ids as # well to avoid breaking the test invariant. To show that the # template limitation really does add a limit on top of the ids # themselves, we also pass the id of another message in a # different template. That message is not deleted. (new_nl_message, new_de_message) = self._makeMessages( "Foo", "Foe", "Fu") unrelated_nl_pofile = self.factory.makePOFile('nl') potmsgset = self.factory.makePOTMsgSet( unrelated_nl_pofile.potemplate, 'Foo', sequence=0) unrelated_nl_message = self.factory.makeCurrentTranslationMessage( unrelated_nl_pofile, potmsgset, translator=unrelated_nl_pofile.potemplate.owner, translations={0: "Foe"}) ids = [new_nl_message.id, new_de_message.id, unrelated_nl_message.id] self._removeMessages( ids=ids, potemplate=self.potemplate.id) self._checkInvariant() self.assertEqual(self._getContents(unrelated_nl_pofile), ["Foe"]) def test_RemoveByLanguage(self): # Remove messages by language. Pass the ids of one Dutch # message and one German message, but specify Dutch as the # language to delete from; only the Dutch message is deleted. potmsgset = self.factory.makePOTMsgSet(self.potemplate, 'Bar', sequence=0) message = self._setTranslation(potmsgset, self.nl_pofile, 'Cafe') self._removeMessages( ids=[message.id, self.de_message.id], language_code='nl') self._checkInvariant() def test_RemoveByNotLanguage(self): # Remove messages, but spare otherwise matching messages that # are in German. potmsgset = self.factory.makePOTMsgSet(self.potemplate, 'Hi', sequence=0) message = self._setTranslation(potmsgset, self.nl_pofile, 'Hoi') self._removeMessages( ids=[message.id, self.de_message.id], language_code='de', not_language=True) self._checkInvariant() def test_RemoveCurrent(self): # Remove current messages, but not non-current messages. (new_nl_message, new_de_message) = self._makeMessages( "translate", "vertalen", "uebersetzen") self.nl_message.is_current_upstream = False messages_to_delete = [self.nl_message, new_nl_message, new_de_message] current_upstream_messages = [ message for message in messages_to_delete if message.is_current_upstream] ids = [message.id for message in messages_to_delete] logger = logging.getLogger('test_remove_translations') logger.setLevel(logging.WARN) loghandler = Handler(self) loghandler.add(logger.name) self._removeMessages(ids=ids, is_current_upstream=True, logger=logger) self.nl_message.is_current_upstream = True self._checkInvariant() loghandler.assertLogsMessage( 'Deleting messages currently in use:', level=logging.WARN) for message in current_upstream_messages: loghandler.assertLogsMessage( 'Message %i is a current translation in upstream' % message.id, level=logging.WARN) self.assertEqual( 1 + len(current_upstream_messages), len(loghandler.records)) def test_RemoveNotCurrent(self): # Remove current messages, but not non-current messages. (new_nl_message, new_de_message) = self._makeMessages( "write", "schrijven", "schreiben") new_nl_message.is_current_upstream = False new_de_message.is_current_upstream = False ids = [self.nl_message.id, new_nl_message.id, new_de_message.id] self._removeMessages(ids=ids, is_current_upstream=False) self._checkInvariant() def test_RemoveImported(self): # Remove current messages, but not non-current messages. (new_nl_message, new_de_message) = self._makeMessages( "book", "boek", "Buch") new_nl_message.is_current_ubuntu = True new_de_message.is_current_ubuntu = True ids = [self.nl_message.id, new_nl_message.id, new_de_message.id] self._removeMessages(ids=ids, is_current_ubuntu=True) self._checkInvariant() def test_RemoveNotImported(self): # Remove current messages, but not non-current messages. (new_nl_message, new_de_message) = self._makeMessages( "helicopter", "helikopter", "Hubschauber") self.nl_message.is_current_ubuntu = True ids = [self.nl_message.id, new_nl_message.id, new_de_message.id] self._removeMessages(ids=ids, is_current_ubuntu=False) self.nl_message.is_current_ubuntu = False self._checkInvariant() def test_RemoveMsgId(self): # Remove translations by msgid_singular. (new_nl_message, new_de_message) = self._makeMessages( "save", "bewaren", "speichern") self._removeMessages(msgid_singular="save") self._checkInvariant() def test_RemoveOrigin(self): # Remove translations by origin. self.assertEqual( self.nl_message.origin, RosettaTranslationOrigin.ROSETTAWEB) (new_nl_message, new_de_message) = self._makeMessages( "new", "nieuw", "neu", is_current_upstream=True) removeSecurityProxy(new_nl_message).origin = ( RosettaTranslationOrigin.SCM) removeSecurityProxy(new_de_message).origin = ( RosettaTranslationOrigin.SCM) self._removeMessages( potemplate=self.potemplate, origin=RosettaTranslationOrigin.SCM) self._checkInvariant() def test_remove_by_license_rejection(self): # Remove translations submitted by users who rejected the # licensing agreement. refusenik = self.factory.makePerson() self._makeMessages( "Don't download this song", "Niet delen", "Nicht teilen", submitter=refusenik) TranslationRelicensingAgreement( person=refusenik, allow_relicensing=False) self._removeMessages(reject_license=True) self._checkInvariant() def test_remove_unlicensed_none(self): # Removing translations whose submitters rejected our # translations licence does not affect translations by those who # haven't answered the question yet. self._removeMessages(reject_license=True) self._checkInvariant() def test_remove_unlicensed_when_licensed(self): # Removing translations whose submitters rejected our # translations licence does not affect translations by those who # agreed to license. answer = TranslationRelicensingAgreement( person=self.nl_message.submitter, allow_relicensing=True) try: self._removeMessages(reject_license=True) self._checkInvariant() finally: # Clean up. answer.destroySelf() def test_remove_unlicensed_restriction(self): # When removing unlicensed translations, other restrictions # still apply. self.nl_message.is_current_upstream = True self.de_message.is_current_upstream = True answer = TranslationRelicensingAgreement( person=self.nl_message.submitter, allow_relicensing=False) try: self._removeMessages( reject_license=True, is_current_upstream=False) self._checkInvariant() finally: # Clean up. answer.destroySelf()
class TestRemoveTranslationsOptionsHandling(TestCase): """Test `RemoveTranslations`' options parsing and type checking.""" layer = LaunchpadZopelessLayer def setUp(self): super(TestRemoveTranslationsOptionsHandling, self).setUp() self.factory = LaunchpadObjectFactory() def test_WithNativeArgs(self): # Options can be passed as the string representations of the # types the script wants them in. options = parse_opts([ '--submitter=1', '--reviewer=2', '--id=3', '--id=4', '--potemplate=5', '--language=te', '--not-language', '--is-current-ubuntu=True', '--is-current-upstream=False', '--msgid=Hello', '--origin=1', '--force', ]) self.assertThat(options, MatchesStructure.byEquality( submitter=1, reviewer=2, ids=[3, 4], potemplate=5, language='te', not_language=True, is_current_ubuntu=True, is_current_upstream=False, origin=1, force=True)) def test_WithLookups(self): # The script can also look up some items from different # representations: person names, numbers or different case # settings for booleans, and translation origin identifiers. submitter = self.factory.makePerson() reviewer = self.factory.makePerson() options = parse_opts([ '--submitter=%s' % submitter.name, '--reviewer=%s' % reviewer.name, '--is-current-ubuntu=0', '--is-current-upstream=true', '--origin=SCM', ]) self.assertThat(options, MatchesStructure.byEquality( submitter=submitter.id, reviewer=reviewer.id, is_current_ubuntu=False, is_current_upstream=True, origin=RosettaTranslationOrigin.SCM.value)) def test_BadBool(self): self.assertRaises(Exception, parse_opts, '--is-current-ubuntu=None') def test_UnknownPerson(self): self.assertRaises( Exception, parse_opts, '--reviewer=unknownnonexistentpersonbird') def test_UnknownOrigin(self): self.assertRaises(Exception, parse_opts, '--origin=GAGA')
class TestBranchPopupWidget(unittest.TestCase): """Tests for the branch popup widget.""" layer = LaunchpadFunctionalLayer def assertIs(self, first, second): """Assert `first` is `second`.""" self.assertTrue(first is second, "%r is not %r" % (first, second)) def installLaunchBag(self, user=None, product=None): bag = DummyLaunchBag(user, product) provideUtility(bag, ILaunchBag) return bag def makeBranchPopup(self, vocabulary=None): # Pick a random, semi-appropriate context. context = self.factory.makeProduct() if vocabulary is None: vocabulary = BranchVocabulary(context) request = self.makeRequest() return BranchPopupWidget( self.makeField(context, vocabulary), vocabulary, request) def makeField(self, context, vocabulary): field = Choice( title=_('Branch'), vocabulary=vocabulary, required=False, description=_("The Bazaar branch.")) field.context = context return field def makeRequest(self): return LaunchpadTestRequest() def setUp(self): login(ANONYMOUS) self._original_launch_bag = getUtility(ILaunchBag) self.factory = LaunchpadObjectFactory() self.launch_bag = self.installLaunchBag( user=self.factory.makePerson(), product=self.factory.makeProduct()) self.popup = self.makeBranchPopup() def tearDown(self): provideUtility(self._original_launch_bag, ILaunchBag) logout() def test_getProduct(self): """getProduct() returns the product in the LaunchBag.""" self.assertEqual(self.launch_bag.product, self.popup.getProduct()) def test_getPerson(self): """getPerson() returns the logged-in user.""" self.assertEqual(self.launch_bag.user, self.popup.getPerson()) def test_getBranchNameFromURL(self): """getBranchNameFromURL() gets a branch name from a url. In general, the name is the last path segment of the URL. """ url = self.factory.getUniqueURL() name = self.popup.getBranchNameFromURL(url) self.assertEqual(URI(url).path.split('/')[-1], name) def test_makeBranchFromURL(self): """makeBranchFromURL(url) creates a mirrored branch at `url`. The owner and registrant are the currently logged-in user, as given by getPerson(), and the product is the product in the LaunchBag. """ url = self.factory.getUniqueURL() expected_name = self.popup.getBranchNameFromURL(url) branch = self.popup.makeBranchFromURL(url) self.assertEqual(BranchType.MIRRORED, branch.branch_type) self.assertEqual(url, branch.url) self.assertEqual(self.popup.getPerson(), branch.owner) self.assertEqual(self.popup.getPerson(), branch.registrant) self.assertEqual(self.popup.getProduct(), branch.product) self.assertEqual(expected_name, branch.name) def test_makeBranch_used(self): # makeBranch makes up the branch name if the inferred one is already # used. url = self.factory.getUniqueURL() expected_name = self.popup.getBranchNameFromURL(url) self.factory.makeProductBranch( name=expected_name, product=self.popup.getProduct(), owner=self.popup.getPerson()) branch = self.popup.makeBranchFromURL(url) self.assertEqual(expected_name + '-1', branch.name) def test_makeBranchRequestsMirror(self): """makeBranch requests a mirror on the branch it creates.""" url = self.factory.getUniqueURL() branch = self.popup.makeBranchFromURL(url) self.assertNotEqual('None', str(branch.next_mirror_time)) def test_makeBranchNoProduct(self): """makeBranchFromURL(url) returns None if there's no product. Not all contexts for branch registration have products. In particular, a bug can be on a source package. When we link a branch to that bug, there's no clear product to choose, so we don't choose any. """ self.installLaunchBag(product=None, user=self.factory.makePerson()) url = self.factory.getUniqueURL() self.assertRaises(NoProductError, self.popup.makeBranchFromURL, url) def test_makeBranchTrailingSlash(self): """makeBranch creates a mirrored branch even if the URL ends with /. """ uri = URI(self.factory.getUniqueURL()) expected_name = self.popup.getBranchNameFromURL( str(uri.ensureNoSlash())) branch = self.popup.makeBranchFromURL(str(uri.ensureSlash())) self.assertEqual(str(uri.ensureNoSlash()), branch.url) self.assertEqual(expected_name, branch.name) def test_toFieldValueFallsBackToMakingBranch(self): """_toFieldValue falls back to making a branch if it's given a URL.""" url = self.factory.getUniqueURL() # Check that there's no branch with this URL. self.assertIs(None, getUtility(IBranchLookup).getByUrl(url)) branch = self.popup._toFieldValue(url) self.assertEqual(url, branch.url) def test_toFieldValueFetchesTheExistingBranch(self): """_toFieldValue returns the existing branch that has that URL.""" expected_branch = self.factory.makeAnyBranch( branch_type=BranchType.MIRRORED) branch = self.popup._toFieldValue(expected_branch.url) self.assertEqual(expected_branch, branch) def test_toFieldValueNonURL(self): """When the input isn't a URL, fall back to the original error.""" empty_search = 'doesntexist' self.assertRaises( ConversionError, self.popup._toFieldValue, empty_search) def test_toFieldValueNoProduct(self): """When there's no product, fall back to the original error.""" self.installLaunchBag(product=None, user=self.factory.makePerson()) self.assertRaises( ConversionError, self.popup._toFieldValue, self.factory.getUniqueURL()) def test_toFieldBadURL(self): """When the input is a bad URL, fall back to the original error. There are many valid URLs that are inappropriate for a mirrored branch. We don't want to register a mirrored branch when someone enters such a URL. """ bad_url = 'svn://svn.example.com/repo/trunk' self.assertRaises(ConversionError, self.popup._toFieldValue, bad_url) def test_branchInRestrictedProduct(self): # There are two reasons for a URL not being in the vocabulary. One # reason is that it's there's no registered branch with that URL. The # other is that the vocabulary on this form is restricted to one # product, and there *is* a branch with that URL, but it's registered # on a different product. # Make a popup restricted to a particular product. vocab = BranchRestrictedOnProductVocabulary(self.launch_bag.product) self.assertEqual(vocab.product, self.launch_bag.product) popup = self.makeBranchPopup(vocab) # Make a branch on a different product. branch = self.factory.makeProductBranch( branch_type=BranchType.MIRRORED) self.assertNotEqual(self.launch_bag.product, branch.product) # Trying to make a branch with that URL will fail. self.assertRaises(ConversionError, popup._toFieldValue, branch.url)