def _setUpLaunchpadServer(self, user_id, codehosting_api, backing_transport): server = LaunchpadServer( XMLRPCWrapper(codehosting_api), user_id, backing_transport) server.start_server() self.addCleanup(server.stop_server) return server
def _setUpLaunchpadServer(self, user_id, codehosting_api, backing_transport): server = LaunchpadServer(XMLRPCWrapper(codehosting_api), user_id, backing_transport) server.start_server() self.addCleanup(server.stop_server) return server
def get_server(self): if self._server is None: self._server = LaunchpadServer(XMLRPCWrapper(self.codehosting_api), self.requester.id, self.backing_transport) self._server.start_server() self.addCleanup(self._server.stop_server) return self._server
def setUp(self): super(TestFilesystem, self).setUp() self.disable_directory_isolation() frontend = InMemoryFrontend() self.factory = frontend.getLaunchpadObjectFactory() endpoint = XMLRPCWrapper(frontend.getCodehostingEndpoint()) self.requester = self.factory.makePerson() self._server = LaunchpadServer( endpoint, self.requester.id, MemoryTransport()) self._server.start_server() self.addCleanup(self._server.stop_server)
def get_server(self): if self._server is None: self._server = LaunchpadServer( XMLRPCWrapper(self.codehosting_api), self.requester.id, self.backing_transport) self._server.start_server() self.addCleanup(self._server.stop_server) return self._server
class TestBranchChangedErrorHandling(TestCaseWithTransport, TestCase): """Test handling of errors when branchChange is called.""" def setUp(self): super(TestBranchChangedErrorHandling, self).setUp() self._server = None frontend = InMemoryFrontend() self.factory = frontend.getLaunchpadObjectFactory() self.codehosting_api = frontend.getCodehostingEndpoint() self.codehosting_api.branchChanged = self._replacement_branchChanged self.requester = self.factory.makePerson() self.backing_transport = MemoryTransport() self.disable_directory_isolation() # Trap stderr. self.addCleanup(setattr, sys, 'stderr', sys.stderr) self._real_stderr = sys.stderr sys.stderr = codecs.getwriter('utf8')(StringIO()) # To record generated oopsids self.generated_oopsids = [] def _replacement_branchChanged(self, user_id, branch_id, stacked_on_url, last_revision, *format_strings): """Log an oops and raise an xmlrpc fault.""" request = errorlog.ScriptRequest([('source', branch_id), ('error-explanation', "An error occurred")]) try: raise TimeoutError() except TimeoutError: f = sys.exc_info() report = errorlog.globalErrorUtility.raising(f, request) # Record the id for checking later. self.generated_oopsids.append(report['id']) raise xmlrpclib.Fault(-1, report) def get_server(self): if self._server is None: self._server = LaunchpadServer(XMLRPCWrapper(self.codehosting_api), self.requester.id, self.backing_transport) self._server.start_server() self.addCleanup(self._server.stop_server) return self._server def test_branchChanged_stderr_text(self): # An unexpected error invoking branchChanged() results in a user # friendly error printed to stderr (and not a traceback). # Unlocking a branch calls branchChanged x 2 on the branch filesystem # endpoint. We will then check the error handling. db_branch = self.factory.makeAnyBranch(branch_type=BranchType.HOSTED, owner=self.requester) branch = run_with_log_observers([], self.make_branch, db_branch.unique_name) branch.lock_write() branch.unlock() stderr_text = sys.stderr.getvalue() # The text printed to stderr should be like this: # (we need the prefix text later for extracting the oopsid) expected_fault_text_prefix = """ <Fault 380: 'An unexpected error has occurred while updating a Launchpad branch. Please report a Launchpad bug and quote:""" expected_fault_text = expected_fault_text_prefix + " OOPS-.*'>" # For our test case, branchChanged() is called twice, hence 2 errors. expected_stderr = ' '.join([expected_fault_text for x in range(2)]) self.assertTextMatchesExpressionIgnoreWhitespace( expected_stderr, stderr_text) # Extract an oops id from the std error text. # There will be 2 oops ids. The 2nd will be the oops for the last # logged error report and the 1st will be in the error text from the # error report. oopsids = [] stderr_text = ' '.join(stderr_text.split()) expected_fault_text_prefix = ' '.join( expected_fault_text_prefix.split()) parts = re.split(expected_fault_text_prefix, stderr_text) for txt in parts: if len(txt) == 0: continue txt = txt.strip() # The oopsid ends with a '.' oopsid = txt[:txt.find('.')] oopsids.append(oopsid) # Now check the error report - we just check the last one. self.assertEqual(len(oopsids), 2) error_report = self.oopses[-1] # The error report oopsid should match what's print to stderr. self.assertEqual(error_report['id'], oopsids[1]) # The error report text should contain the root cause oopsid. self.assertContainsString(error_report['tb_text'], self.generated_oopsids[1])
class TestBranchChangedNotification(TestCaseWithTransport): """Test notification of branch changes.""" def setUp(self): super(TestBranchChangedNotification, self).setUp() self._server = None self._branch_changed_log = [] frontend = InMemoryFrontend() self.factory = frontend.getLaunchpadObjectFactory() self.codehosting_api = frontend.getCodehostingEndpoint() self.codehosting_api.branchChanged = self._replacement_branchChanged self.requester = self.factory.makePerson() self.backing_transport = MemoryTransport() self.disable_directory_isolation() def _replacement_branchChanged(self, user_id, branch_id, stacked_on_url, last_revision, *format_strings): self._branch_changed_log.append( dict(user_id=user_id, branch_id=branch_id, stacked_on_url=stacked_on_url, last_revision=last_revision, format_strings=format_strings)) def get_server(self): if self._server is None: self._server = LaunchpadServer(XMLRPCWrapper(self.codehosting_api), self.requester.id, self.backing_transport) self._server.start_server() self.addCleanup(self._server.stop_server) return self._server def test_no_mirrors_requested_if_no_branches_changed(self): self.assertEqual([], self._branch_changed_log) def test_creating_branch_calls_branchChanged(self): # Creating a branch requests a mirror. db_branch = self.factory.makeAnyBranch(branch_type=BranchType.HOSTED, owner=self.requester) self.make_branch(db_branch.unique_name) self.assertEqual(1, len(self._branch_changed_log)) def test_branch_unlock_calls_branchChanged(self): # Unlocking a branch calls branchChanged on the branch filesystem # endpoint. db_branch = self.factory.makeAnyBranch(branch_type=BranchType.HOSTED, owner=self.requester) branch = self.make_branch(db_branch.unique_name) del self._branch_changed_log[:] branch.lock_write() branch.unlock() self.assertEqual(1, len(self._branch_changed_log)) def test_branch_unlock_reports_users_id(self): # Unlocking a branch calls branchChanged on the branch filesystem # endpoint with the logged in user's id. db_branch = self.factory.makeAnyBranch(branch_type=BranchType.HOSTED, owner=self.requester) branch = self.make_branch(db_branch.unique_name) del self._branch_changed_log[:] branch.lock_write() branch.unlock() self.assertEqual(1, len(self._branch_changed_log)) self.assertEqual(self.requester.id, self._branch_changed_log[0]['user_id']) def test_branch_unlock_reports_stacked_on_url(self): # Unlocking a branch reports the stacked on URL to the branch # filesystem endpoint. db_branch1 = self.factory.makeAnyBranch(branch_type=BranchType.HOSTED, owner=self.requester) db_branch2 = self.factory.makeAnyBranch(branch_type=BranchType.HOSTED, owner=self.requester) self.make_branch(db_branch1.unique_name) branch = self.make_branch(db_branch2.unique_name) del self._branch_changed_log[:] branch.lock_write() branch.set_stacked_on_url('/' + db_branch1.unique_name) branch.unlock() self.assertEqual(1, len(self._branch_changed_log)) self.assertEqual('/' + db_branch1.unique_name, self._branch_changed_log[0]['stacked_on_url']) def test_branch_unlock_reports_last_revision(self): # Unlocking a branch reports the tip revision of the branch to the # branch filesystem endpoint. db_branch = self.factory.makeAnyBranch(branch_type=BranchType.HOSTED, owner=self.requester) branch = self.make_branch(db_branch.unique_name) revid = branch.create_checkout('tree').commit('') del self._branch_changed_log[:] branch.lock_write() branch.unlock() self.assertEqual(1, len(self._branch_changed_log)) self.assertEqual(revid, self._branch_changed_log[0]['last_revision']) def assertStackedOnIsRewritten(self, input, output): db_branch = self.factory.makeAnyBranch(branch_type=BranchType.HOSTED, owner=self.requester) branch = self.make_branch(db_branch.unique_name) del self._branch_changed_log[:] branch.lock_write() branch._set_config_location('stacked_on_location', input) branch.unlock() # Clear the branch config cache to pick up the changes we made # directly to the filesystem. branch._get_config_store().unload() self.assertEqual(output, branch.get_stacked_on_url()) self.assertEqual(1, len(self._branch_changed_log)) self.assertEqual(output, self._branch_changed_log[0]['stacked_on_url']) def test_branch_unlock_relativizes_absolute_stacked_on_url(self): # When a branch that has been stacked on the absolute URL of another # Launchpad branch is unlocked, the branch is mutated to be stacked on # the path part of that URL, and this relative path is passed to # branchChanged(). self.assertStackedOnIsRewritten( 'http://bazaar.launchpad.dev/~user/product/branch', '/~user/product/branch') def test_branch_unlock_ignores_non_launchpad_stacked_url(self): # When a branch that has been stacked on the absolute URL of a branch # that is not on Launchpad, it is passed unchanged to branchChanged(). self.assertStackedOnIsRewritten('http://example.com/~user/foo', 'http://example.com/~user/foo') def test_branch_unlock_ignores_odd_scheme_stacked_url(self): # When a branch that has been stacked on the absolute URL of a branch # on Launchpad with a scheme we don't understand, it is passed # unchanged to branchChanged(). self.assertStackedOnIsRewritten( 'gopher://bazaar.launchpad.dev/~user/foo', 'gopher://bazaar.launchpad.dev/~user/foo') def assertFormatStringsPassed(self, branch): self.assertEqual(1, len(self._branch_changed_log)) control_string = branch.bzrdir._format.get_format_string() branch_string = branch._format.get_format_string() repository_string = branch.repository._format.get_format_string() self.assertEqual((control_string, branch_string, repository_string), self._branch_changed_log[0]['format_strings']) def test_format_2a(self): # Creating a 2a branch reports the format to branchChanged. db_branch = self.factory.makeAnyBranch(branch_type=BranchType.HOSTED, owner=self.requester) branch = self.make_branch(db_branch.unique_name, format=format_registry.get('2a')()) self.assertFormatStringsPassed(branch)
def getServer(self, codehosting_api, user_id, backing_transport): return LaunchpadServer(XMLRPCWrapper(codehosting_api), user_id, backing_transport)
def getLaunchpadServer(self, codehosting_api, user_id): return LaunchpadServer(XMLRPCWrapper(codehosting_api), user_id, MemoryTransport())
class TestFilesystem(TestCaseWithTransport): # XXX: JonathanLange 2008-10-07 bug=267013: Many of these tests duplicate # tests in test_branchfs and test_transport. We should review the tests # and remove the ones that aren't needed. def setUp(self): super(TestFilesystem, self).setUp() self.disable_directory_isolation() frontend = InMemoryFrontend() self.factory = frontend.getLaunchpadObjectFactory() endpoint = XMLRPCWrapper(frontend.getCodehostingEndpoint()) self.requester = self.factory.makePerson() self._server = LaunchpadServer( endpoint, self.requester.id, MemoryTransport()) self._server.start_server() self.addCleanup(self._server.stop_server) def getTransport(self, relpath=None): return get_transport(self._server.get_url()).clone(relpath) def test_remove_branch_directory(self): # Make some directories under ~testuser/+junk (i.e. create some empty # branches) transport = self.getTransport('~%s/+junk' % self.requester.name) transport.mkdir('foo') transport.mkdir('bar') self.assertTrue(stat.S_ISDIR(transport.stat('foo').st_mode)) self.assertTrue(stat.S_ISDIR(transport.stat('bar').st_mode)) # Try to remove a branch directory, which is not allowed. self.assertRaises( errors.PermissionDenied, transport.rmdir, 'foo') # The 'foo' directory is still listed. self.assertTrue(transport.has('bar')) self.assertTrue(transport.has('foo')) def test_make_invalid_user_directory(self): # The top-level directory must always be of the form '~user'. However, # sometimes a transport will ask to look at files that aren't of that # form. In that case, the transport is denied permission. transport = self.getTransport() self.assertRaises( errors.PermissionDenied, transport.mkdir, 'apple') def test_make_valid_user_directory(self): # Making a top-level directory is not supported by the Launchpad # transport. transport = self.getTransport() self.assertRaises( errors.PermissionDenied, transport.mkdir, '~apple') def test_make_existing_user_directory(self): # Making a user directory raises an error. We don't really care what # the error is, but it should be one of FileExists, # TransportNotPossible or NoSuchFile transport = self.getTransport() self.assertRaises( errors.PermissionDenied, transport.mkdir, '~%s' % self.requester.name) def test_mkdir_not_team_member_error(self): # You can't make a branch under the directory of a team that you don't # belong to. team = self.factory.makeTeam(self.factory.makePerson()) product = self.factory.makeProduct() transport = self.getTransport() self.assertRaises( errors.PermissionDenied, transport.mkdir, '~%s/%s/new-branch' % (team.name, product.name)) def test_make_team_branch_directory(self): # You can make a branch directory under a team directory that you are # a member of (so long as it's a real product). team = self.factory.makeTeam(self.requester) product = self.factory.makeProduct() transport = self.getTransport() transport.mkdir('~%s/%s/shiny-new-thing' % (team.name, product.name)) self.assertTrue( transport.has( '~%s/%s/shiny-new-thing' % (team.name, product.name))) def test_make_product_directory_for_nonexistent_product(self): # Making a branch directory for a non-existent product is not allowed. # Products must first be registered in Launchpad. transport = self.getTransport() self.assertRaises( errors.PermissionDenied, transport.mkdir, '~%s/no-such-product/new-branch' % self.requester.name) def test_make_branch_directory(self): # We allow users to create new branches by pushing them beneath an # existing product directory. product = self.factory.makeProduct() transport = self.getTransport() branch_path = '~%s/%s/banana' % (self.requester.name, product.name) transport.mkdir(branch_path) self.assertTrue(transport.has(branch_path)) def test_make_junk_branch(self): # Users can make branches beneath their '+junk' folder. transport = self.getTransport() branch_path = '~%s/+junk/banana' % self.requester.name transport.mkdir(branch_path) self.assertTrue(transport.has(branch_path)) def test_get_stacking_policy(self): # A stacking policy control file is served underneath product # directories for products that have a default stacked-on branch. product = self.factory.makeProduct() self.factory.enableDefaultStackingForProduct(product) transport = self.getTransport() control_file = transport.get_bytes( '~%s/%s/.bzr/control.conf' % (self.requester.name, product.name)) stacked_on = IBranchTarget(product).default_stacked_on_branch self.assertEqual( 'default_stack_on = %s' % branch_id_alias(stacked_on), control_file.strip()) def test_can_open_product_control_dir(self): # The stacking policy lives in a bzrdir in the product directory. # Bazaar needs to be able to open this bzrdir. product = self.factory.makeProduct() self.factory.enableDefaultStackingForProduct(product) transport = self.getTransport().clone( '~%s/%s' % (self.requester.name, product.name)) found_bzrdir = BzrDir.open_from_transport(transport) # We really just want to test that the above line doesn't raise an # exception. However, we'll also check that we get the bzrdir that we # expected. expected_url = transport.clone('.bzr').base self.assertEqual(expected_url, found_bzrdir.transport.base) def test_directory_inside_branch(self): # We allow users to create new branches by pushing them beneath an # existing product directory. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) transport.mkdir('%s/.bzr' % branch_path) self.assertTrue(transport.has(branch_path)) self.assertTrue(transport.has('%s/.bzr' % branch_path)) def test_bzr_backup_directory_inside_branch(self): # Bazaar sometimes needs to create .bzr.backup directories directly # underneath the branch directory. Thus, we allow the creation of # .bzr.backup directories. The .bzr.backup directory is a deprecated # name. Now Bazaar uses 'backup.bzr'. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) transport.mkdir('%s/.bzr.backup' % branch_path) self.assertTrue(transport.has(branch_path)) self.assertTrue(transport.has('%s/.bzr.backup' % branch_path)) def test_backup_bzr_directory_inside_branch(self): # Bazaar sometimes needs to create backup.bzr directories directly # underneath the branch directory. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) transport.mkdir('%s/backup.bzr' % branch_path) self.assertTrue(transport.has(branch_path)) self.assertTrue(transport.has('%s/backup.bzr' % branch_path)) def test_non_bzr_directory_inside_branch(self): # Users can only create Bazaar control directories (e.g. '.bzr') # inside a branch. Other directories are strictly forbidden. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) self.assertRaises( errors.PermissionDenied, transport.mkdir, '%s/not-a-bzr-dir' % branch_path) def test_non_bzr_file_inside_branch(self): # Users can only create Bazaar control directories (e.g. '.bzr') # inside a branch. Files are not allowed. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) self.assertRaises( errors.PermissionDenied, transport.put_bytes, '%s/README' % branch_path, 'Hello!') def test_rename_to_non_bzr_directory_fails(self): # Users cannot create an allowed directory (e.g. '.bzr' or # 'backup.bzr') and then rename it to something that's not allowed # (e.g. 'republic'). product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) transport.mkdir('%s/.bzr' % branch_path) self.assertRaises( errors.PermissionDenied, transport.rename, '%s/.bzr' % branch_path, '%s/not-a-branch-dir' % branch_path) def test_make_directory_without_prefix(self): # Because the user and product directories don't exist on the # filesystem, we can create a branch directory for a product even if # there are no existing branches for that product. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) self.assertTrue(transport.has(branch_path)) def _getBzrDirTransport(self): """Make a .bzr directory in a branch and return a transport for it. We use this to test filesystem behaviour beneath the .bzr directory of a branch, which generally has fewer constraints and exercises different code paths. """ product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) transport = transport.clone(branch_path) transport.mkdir('.bzr') return transport.clone('.bzr') def test_rename_directory_to_existing_directory_fails(self): # 'rename dir1 dir2' should fail if 'dir2' exists. Unfortunately, it # will only fail if they both contain files/directories. transport = self._getBzrDirTransport() transport.mkdir('dir1') transport.mkdir('dir1/foo') transport.mkdir('dir2') transport.mkdir('dir2/bar') self.assertRaises(errors.FileExists, transport.rename, 'dir1', 'dir2') def test_rename_directory_succeeds(self): # 'rename dir1 dir2' succeeds if 'dir2' doesn't exist. transport = self._getBzrDirTransport() transport.mkdir('dir1') transport.mkdir('dir1/foo') transport.rename('dir1', 'dir2') self.assertEqual(['dir2'], transport.list_dir('.')) def test_make_directory_twice(self): # The transport raises a `FileExists` error if we try to make a # directory that already exists. transport = self._getBzrDirTransport() transport.mkdir('dir1') self.assertRaises(errors.FileExists, transport.mkdir, 'dir1') def test_url_escaping(self): # Transports accept and return escaped URL segments. The literal path # we use should be preserved, even if it can be unescaped itself. transport = self._getBzrDirTransport() # The bug we are checking only occurs if # unescape(path).encode('utf-8') != path. path = '%41%42%43' escaped_path = escape(path) content = 'content' transport.put_bytes(escaped_path, content) # We can use the escaped path to reach the file. self.assertEqual(content, transport.get_bytes(escaped_path)) # We can also use the value that list_dir returns, which may be # different from our original escaped path. Note that in this case, # returned_path is equivalent but not equal to escaped_path. [returned_path] = list(transport.list_dir('.')) self.assertEqual(content, transport.get_bytes(returned_path))
class TestFilesystem(TestCaseWithTransport): # XXX: JonathanLange 2008-10-07 bug=267013: Many of these tests duplicate # tests in test_branchfs and test_transport. We should review the tests # and remove the ones that aren't needed. def setUp(self): super(TestFilesystem, self).setUp() self.disable_directory_isolation() frontend = InMemoryFrontend() self.factory = frontend.getLaunchpadObjectFactory() endpoint = XMLRPCWrapper(frontend.getCodehostingEndpoint()) self.requester = self.factory.makePerson() self._server = LaunchpadServer( endpoint, self.requester.id, MemoryTransport()) self._server.start_server() self.addCleanup(self._server.stop_server) def getTransport(self, relpath=None): return get_transport(self._server.get_url()).clone(relpath) def test_remove_branch_directory(self): # Make some directories under ~testuser/+junk (i.e. create some empty # branches) transport = self.getTransport('~%s/+junk' % self.requester.name) transport.mkdir('foo') transport.mkdir('bar') self.failUnless(stat.S_ISDIR(transport.stat('foo').st_mode)) self.failUnless(stat.S_ISDIR(transport.stat('bar').st_mode)) # Try to remove a branch directory, which is not allowed. self.assertRaises( errors.PermissionDenied, transport.rmdir, 'foo') # The 'foo' directory is still listed. self.assertTrue(transport.has('bar')) self.assertTrue(transport.has('foo')) def test_make_invalid_user_directory(self): # The top-level directory must always be of the form '~user'. However, # sometimes a transport will ask to look at files that aren't of that # form. In that case, the transport is denied permission. transport = self.getTransport() self.assertRaises( errors.PermissionDenied, transport.mkdir, 'apple') def test_make_valid_user_directory(self): # Making a top-level directory is not supported by the Launchpad # transport. transport = self.getTransport() self.assertRaises( errors.PermissionDenied, transport.mkdir, '~apple') def test_make_existing_user_directory(self): # Making a user directory raises an error. We don't really care what # the error is, but it should be one of FileExists, # TransportNotPossible or NoSuchFile transport = self.getTransport() self.assertRaises( errors.PermissionDenied, transport.mkdir, '~%s' % self.requester.name) def test_mkdir_not_team_member_error(self): # You can't make a branch under the directory of a team that you don't # belong to. team = self.factory.makeTeam(self.factory.makePerson()) product = self.factory.makeProduct() transport = self.getTransport() self.assertRaises( errors.PermissionDenied, transport.mkdir, '~%s/%s/new-branch' % (team.name, product.name)) def test_make_team_branch_directory(self): # You can make a branch directory under a team directory that you are # a member of (so long as it's a real product). team = self.factory.makeTeam(self.requester) product = self.factory.makeProduct() transport = self.getTransport() transport.mkdir('~%s/%s/shiny-new-thing' % (team.name, product.name)) self.assertTrue( transport.has( '~%s/%s/shiny-new-thing' % (team.name, product.name))) def test_make_product_directory_for_nonexistent_product(self): # Making a branch directory for a non-existent product is not allowed. # Products must first be registered in Launchpad. transport = self.getTransport() self.assertRaises( errors.PermissionDenied, transport.mkdir, '~%s/no-such-product/new-branch' % self.requester.name) def test_make_branch_directory(self): # We allow users to create new branches by pushing them beneath an # existing product directory. product = self.factory.makeProduct() transport = self.getTransport() branch_path = '~%s/%s/banana' % (self.requester.name, product.name) transport.mkdir(branch_path) self.assertTrue(transport.has(branch_path)) def test_make_junk_branch(self): # Users can make branches beneath their '+junk' folder. transport = self.getTransport() branch_path = '~%s/+junk/banana' % self.requester.name transport.mkdir(branch_path) self.assertTrue(transport.has(branch_path)) def test_get_stacking_policy(self): # A stacking policy control file is served underneath product # directories for products that have a default stacked-on branch. product = self.factory.makeProduct() self.factory.enableDefaultStackingForProduct(product) transport = self.getTransport() control_file = transport.get_bytes( '~%s/%s/.bzr/control.conf' % (self.requester.name, product.name)) stacked_on = IBranchTarget(product).default_stacked_on_branch self.assertEqual( 'default_stack_on = %s' % branch_id_alias(stacked_on), control_file.strip()) def test_can_open_product_control_dir(self): # The stacking policy lives in a bzrdir in the product directory. # Bazaar needs to be able to open this bzrdir. product = self.factory.makeProduct() self.factory.enableDefaultStackingForProduct(product) transport = self.getTransport().clone( '~%s/%s' % (self.requester.name, product.name)) found_bzrdir = BzrDir.open_from_transport(transport) # We really just want to test that the above line doesn't raise an # exception. However, we'll also check that we get the bzrdir that we # expected. expected_url = transport.clone('.bzr').base self.assertEqual(expected_url, found_bzrdir.transport.base) def test_directory_inside_branch(self): # We allow users to create new branches by pushing them beneath an # existing product directory. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) transport.mkdir('%s/.bzr' % branch_path) self.assertTrue(transport.has(branch_path)) self.assertTrue(transport.has('%s/.bzr' % branch_path)) def test_bzr_backup_directory_inside_branch(self): # Bazaar sometimes needs to create .bzr.backup directories directly # underneath the branch directory. Thus, we allow the creation of # .bzr.backup directories. The .bzr.backup directory is a deprecated # name. Now Bazaar uses 'backup.bzr'. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) transport.mkdir('%s/.bzr.backup' % branch_path) self.assertTrue(transport.has(branch_path)) self.assertTrue(transport.has('%s/.bzr.backup' % branch_path)) def test_backup_bzr_directory_inside_branch(self): # Bazaar sometimes needs to create backup.bzr directories directly # underneath the branch directory. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) transport.mkdir('%s/backup.bzr' % branch_path) self.assertTrue(transport.has(branch_path)) self.assertTrue(transport.has('%s/backup.bzr' % branch_path)) def test_non_bzr_directory_inside_branch(self): # Users can only create Bazaar control directories (e.g. '.bzr') # inside a branch. Other directories are strictly forbidden. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) self.assertRaises( errors.PermissionDenied, transport.mkdir, '%s/not-a-bzr-dir' % branch_path) def test_non_bzr_file_inside_branch(self): # Users can only create Bazaar control directories (e.g. '.bzr') # inside a branch. Files are not allowed. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) self.assertRaises( errors.PermissionDenied, transport.put_bytes, '%s/README' % branch_path, 'Hello!') def test_rename_to_non_bzr_directory_fails(self): # Users cannot create an allowed directory (e.g. '.bzr' or # 'backup.bzr') and then rename it to something that's not allowed # (e.g. 'republic'). product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) transport.mkdir('%s/.bzr' % branch_path) self.assertRaises( errors.PermissionDenied, transport.rename, '%s/.bzr' % branch_path, '%s/not-a-branch-dir' % branch_path) def test_make_directory_without_prefix(self): # Because the user and product directories don't exist on the # filesystem, we can create a branch directory for a product even if # there are no existing branches for that product. product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) self.assertTrue(transport.has(branch_path)) def _getBzrDirTransport(self): """Make a .bzr directory in a branch and return a transport for it. We use this to test filesystem behaviour beneath the .bzr directory of a branch, which generally has fewer constraints and exercises different code paths. """ product = self.factory.makeProduct() branch_path = '~%s/%s/%s' % ( self.requester.name, product.name, self.factory.getUniqueString()) transport = self.getTransport() transport.mkdir(branch_path) transport = transport.clone(branch_path) transport.mkdir('.bzr') return transport.clone('.bzr') def test_rename_directory_to_existing_directory_fails(self): # 'rename dir1 dir2' should fail if 'dir2' exists. Unfortunately, it # will only fail if they both contain files/directories. transport = self._getBzrDirTransport() transport.mkdir('dir1') transport.mkdir('dir1/foo') transport.mkdir('dir2') transport.mkdir('dir2/bar') self.assertRaises(errors.FileExists, transport.rename, 'dir1', 'dir2') def test_rename_directory_succeeds(self): # 'rename dir1 dir2' succeeds if 'dir2' doesn't exist. transport = self._getBzrDirTransport() transport.mkdir('dir1') transport.mkdir('dir1/foo') transport.rename('dir1', 'dir2') self.assertEqual(['dir2'], transport.list_dir('.')) def test_make_directory_twice(self): # The transport raises a `FileExists` error if we try to make a # directory that already exists. transport = self._getBzrDirTransport() transport.mkdir('dir1') self.assertRaises(errors.FileExists, transport.mkdir, 'dir1') def test_url_escaping(self): # Transports accept and return escaped URL segments. The literal path # we use should be preserved, even if it can be unescaped itself. transport = self._getBzrDirTransport() # The bug we are checking only occurs if # unescape(path).encode('utf-8') != path. path = '%41%42%43' escaped_path = escape(path) content = 'content' transport.put_bytes(escaped_path, content) # We can use the escaped path to reach the file. self.assertEqual(content, transport.get_bytes(escaped_path)) # We can also use the value that list_dir returns, which may be # different from our original escaped path. Note that in this case, # returned_path is equivalent but not equal to escaped_path. [returned_path] = list(transport.list_dir('.')) self.assertEqual(content, transport.get_bytes(returned_path))
class TestBranchChangedNotification(TestCaseWithTransport): """Test notification of branch changes.""" def setUp(self): super(TestBranchChangedNotification, self).setUp() self._server = None self._branch_changed_log = [] frontend = InMemoryFrontend() self.factory = frontend.getLaunchpadObjectFactory() self.codehosting_api = frontend.getCodehostingEndpoint() self.codehosting_api.branchChanged = self._replacement_branchChanged self.requester = self.factory.makePerson() self.backing_transport = MemoryTransport() self.disable_directory_isolation() def _replacement_branchChanged(self, user_id, branch_id, stacked_on_url, last_revision, *format_strings): self._branch_changed_log.append(dict( user_id=user_id, branch_id=branch_id, stacked_on_url=stacked_on_url, last_revision=last_revision, format_strings=format_strings)) def get_server(self): if self._server is None: self._server = LaunchpadServer( XMLRPCWrapper(self.codehosting_api), self.requester.id, self.backing_transport) self._server.start_server() self.addCleanup(self._server.stop_server) return self._server def test_no_mirrors_requested_if_no_branches_changed(self): self.assertEqual([], self._branch_changed_log) def test_creating_branch_calls_branchChanged(self): # Creating a branch requests a mirror. db_branch = self.factory.makeAnyBranch( branch_type=BranchType.HOSTED, owner=self.requester) self.make_branch(db_branch.unique_name) self.assertEqual(1, len(self._branch_changed_log)) def test_branch_unlock_calls_branchChanged(self): # Unlocking a branch calls branchChanged on the branch filesystem # endpoint. db_branch = self.factory.makeAnyBranch( branch_type=BranchType.HOSTED, owner=self.requester) branch = self.make_branch(db_branch.unique_name) del self._branch_changed_log[:] branch.lock_write() branch.unlock() self.assertEqual(1, len(self._branch_changed_log)) def test_branch_unlock_reports_users_id(self): # Unlocking a branch calls branchChanged on the branch filesystem # endpoint with the logged in user's id. db_branch = self.factory.makeAnyBranch( branch_type=BranchType.HOSTED, owner=self.requester) branch = self.make_branch(db_branch.unique_name) del self._branch_changed_log[:] branch.lock_write() branch.unlock() self.assertEqual(1, len(self._branch_changed_log)) self.assertEqual( self.requester.id, self._branch_changed_log[0]['user_id']) def test_branch_unlock_reports_stacked_on_url(self): # Unlocking a branch reports the stacked on URL to the branch # filesystem endpoint. db_branch1 = self.factory.makeAnyBranch( branch_type=BranchType.HOSTED, owner=self.requester) db_branch2 = self.factory.makeAnyBranch( branch_type=BranchType.HOSTED, owner=self.requester) self.make_branch(db_branch1.unique_name) branch = self.make_branch(db_branch2.unique_name) del self._branch_changed_log[:] branch.lock_write() branch.set_stacked_on_url('/' + db_branch1.unique_name) branch.unlock() self.assertEqual(1, len(self._branch_changed_log)) self.assertEqual( '/' + db_branch1.unique_name, self._branch_changed_log[0]['stacked_on_url']) def test_branch_unlock_reports_last_revision(self): # Unlocking a branch reports the tip revision of the branch to the # branch filesystem endpoint. db_branch = self.factory.makeAnyBranch( branch_type=BranchType.HOSTED, owner=self.requester) branch = self.make_branch(db_branch.unique_name) revid = branch.create_checkout('tree').commit('') del self._branch_changed_log[:] branch.lock_write() branch.unlock() self.assertEqual(1, len(self._branch_changed_log)) self.assertEqual( revid, self._branch_changed_log[0]['last_revision']) def assertStackedOnIsRewritten(self, input, output): db_branch = self.factory.makeAnyBranch( branch_type=BranchType.HOSTED, owner=self.requester) branch = self.make_branch(db_branch.unique_name) del self._branch_changed_log[:] branch.lock_write() branch._set_config_location('stacked_on_location', input) branch.unlock() # Clear the branch config cache to pick up the changes we made # directly to the filesystem. branch._get_config_store().unload() self.assertEqual(output, branch.get_stacked_on_url()) self.assertEqual(1, len(self._branch_changed_log)) self.assertEqual(output, self._branch_changed_log[0]['stacked_on_url']) def test_branch_unlock_relativizes_absolute_stacked_on_url(self): # When a branch that has been stacked on the absolute URL of another # Launchpad branch is unlocked, the branch is mutated to be stacked on # the path part of that URL, and this relative path is passed to # branchChanged(). self.assertStackedOnIsRewritten( 'http://bazaar.launchpad.dev/~user/product/branch', '/~user/product/branch') def test_branch_unlock_ignores_non_launchpad_stacked_url(self): # When a branch that has been stacked on the absolute URL of a branch # that is not on Launchpad, it is passed unchanged to branchChanged(). self.assertStackedOnIsRewritten( 'http://example.com/~user/foo', 'http://example.com/~user/foo') def test_branch_unlock_ignores_odd_scheme_stacked_url(self): # When a branch that has been stacked on the absolute URL of a branch # on Launchpad with a scheme we don't understand, it is passed # unchanged to branchChanged(). self.assertStackedOnIsRewritten( 'gopher://bazaar.launchpad.dev/~user/foo', 'gopher://bazaar.launchpad.dev/~user/foo') def assertFormatStringsPassed(self, branch): self.assertEqual(1, len(self._branch_changed_log)) control_string = branch.bzrdir._format.get_format_string() branch_string = branch._format.get_format_string() repository_string = branch.repository._format.get_format_string() self.assertEqual( (control_string, branch_string, repository_string), self._branch_changed_log[0]['format_strings']) def test_format_2a(self): # Creating a 2a branch reports the format to branchChanged. db_branch = self.factory.makeAnyBranch( branch_type=BranchType.HOSTED, owner=self.requester) branch = self.make_branch( db_branch.unique_name, format=format_registry.get('2a')()) self.assertFormatStringsPassed(branch)
class TestBranchChangedErrorHandling(TestCaseWithTransport, TestCase): """Test handling of errors when branchChange is called.""" def setUp(self): super(TestBranchChangedErrorHandling, self).setUp() self._server = None frontend = InMemoryFrontend() self.factory = frontend.getLaunchpadObjectFactory() self.codehosting_api = frontend.getCodehostingEndpoint() self.codehosting_api.branchChanged = self._replacement_branchChanged self.requester = self.factory.makePerson() self.backing_transport = MemoryTransport() self.disable_directory_isolation() # Trap stderr. self.addCleanup(setattr, sys, 'stderr', sys.stderr) self._real_stderr = sys.stderr sys.stderr = codecs.getwriter('utf8')(StringIO()) # To record generated oopsids self.generated_oopsids = [] def _replacement_branchChanged(self, user_id, branch_id, stacked_on_url, last_revision, *format_strings): """Log an oops and raise an xmlrpc fault.""" request = errorlog.ScriptRequest([ ('source', branch_id), ('error-explanation', "An error occurred")]) try: raise TimeoutError() except TimeoutError: f = sys.exc_info() report = errorlog.globalErrorUtility.raising(f, request) # Record the id for checking later. self.generated_oopsids.append(report['id']) raise xmlrpclib.Fault(-1, report) def get_server(self): if self._server is None: self._server = LaunchpadServer( XMLRPCWrapper(self.codehosting_api), self.requester.id, self.backing_transport) self._server.start_server() self.addCleanup(self._server.stop_server) return self._server def test_branchChanged_stderr_text(self): # An unexpected error invoking branchChanged() results in a user # friendly error printed to stderr (and not a traceback). # Unlocking a branch calls branchChanged x 2 on the branch filesystem # endpoint. We will then check the error handling. db_branch = self.factory.makeAnyBranch( branch_type=BranchType.HOSTED, owner=self.requester) branch = run_with_log_observers( [], self.make_branch, db_branch.unique_name) branch.lock_write() branch.unlock() stderr_text = sys.stderr.getvalue() # The text printed to stderr should be like this: # (we need the prefix text later for extracting the oopsid) expected_fault_text_prefix = """ <Fault 380: 'An unexpected error has occurred while updating a Launchpad branch. Please report a Launchpad bug and quote:""" expected_fault_text = expected_fault_text_prefix + " OOPS-.*'>" # For our test case, branchChanged() is called twice, hence 2 errors. expected_stderr = ' '.join([expected_fault_text for x in range(2)]) self.assertTextMatchesExpressionIgnoreWhitespace( expected_stderr, stderr_text) # Extract an oops id from the std error text. # There will be 2 oops ids. The 2nd will be the oops for the last # logged error report and the 1st will be in the error text from the # error report. oopsids = [] stderr_text = ' '.join(stderr_text.split()) expected_fault_text_prefix = ' '.join( expected_fault_text_prefix.split()) parts = re.split(expected_fault_text_prefix, stderr_text) for txt in parts: if len(txt) == 0: continue txt = txt.strip() # The oopsid ends with a '.' oopsid = txt[:txt.find('.')] oopsids.append(oopsid) # Now check the error report - we just check the last one. self.assertEqual(len(oopsids), 2) error_report = self.oopses[-1] # The error report oopsid should match what's print to stderr. self.assertEqual(error_report['id'], oopsids[1]) # The error report text should contain the root cause oopsid. self.assertContainsString( error_report['tb_text'], self.generated_oopsids[1])