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
Esempio n. 4
0
 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
 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)
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)
Esempio n. 10
0
 def getLaunchpadServer(self, codehosting_api, user_id):
     return LaunchpadServer(XMLRPCWrapper(codehosting_api), user_id,
                            MemoryTransport())
Esempio n. 11
0
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))
Esempio n. 13
0
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)
Esempio n. 14
0
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])