Ejemplo n.º 1
0
def signal_handler(sig, frame):
    global processes
    global roslaunch_pids
    global hold
    for p in processes:
        roslaunch_pids.extend(get_roslaunch_pid(p.pid))
    # kill roslaunch's
    for pid in roslaunch_pids:
        try:
            os.kill(pid, signal.SIGHUP)
        except OSError:
            continue
    for pid in roslaunch_pids:
        console.pretty_println("Terminating roslaunch [pid: %d]" % pid,
                               console.bold)
        rocon_python_utils.system.wait_pid(pid)
        #console.pretty_println("Terminated roslaunch [pid: %d]" % pid, console.bold)
    sleep(1)
    if hold:
        try:
            raw_input("Press <Enter> to close terminals...")
        except RuntimeError:
            pass  # this happens when you ctrl-c again instead of enter
    # now kill konsoles
    for p in processes:
        p.terminate()
Ejemplo n.º 2
0
 def shutdown_roslaunch_windows(self, processes, hold):
     """
     Shuts down a roslaunch window cleanly, i.e. it first kills the roslaunch
     processes, then kills the terminal itself.
     """
     roslaunch_pids = []
     for process in processes:
         roslaunch_pids.extend(utils.get_roslaunch_pids(process.pid))
     # kill roslaunch's
     for pid in roslaunch_pids:
         try:
             os.kill(pid, signal.SIGHUP)
         except OSError:
             continue
     for pid in roslaunch_pids:
         console.pretty_println("Terminating roslaunch [pid: %d]" % pid, console.bold)
         rocon_python_utils.system.wait_pid(pid)
         #console.pretty_println("Terminated roslaunch [pid: %d]" % pid, console.bold)
     time.sleep(1)
     if hold:
         try:
             raw_input("Press <Enter> to close terminals...")
         except RuntimeError:
             pass  # this happens when you ctrl-c again instead of enter
     # now kill the terminal itself
     for process in processes:
         try:
             os.killpg(process.pid, signal.SIGTERM)
         except OSError:
             console.warning("Kill signal failed to reach the terminal - typically this means the terminal has already shut down.")
         except TypeError as e:
             console.error("Invalid pid value [%s][%s]" % (str(process.pid), str(e)))
Ejemplo n.º 3
0
def test_rapp_loading():
    print_title('Raising on Invalid Rapp Loading')

    console.pretty_println('Extra Field', console.bold)
    filename = pwd + '/test_rapps/rapp/invalid_loading/invalid_attribute.rapp'
    console.pretty_println(' - %s' % filename)
    assert_raises(InvalidRappException, load_rapp_yaml_from_file, filename)
Ejemplo n.º 4
0
    def test_get_compatible_rapps(self):
        print_title('Test Get Compatible Rapps')

        self.setup(compat_data)
        # string rocon uri test
        console.pretty_println('String Rocon URI Given')
        compat = 'rocon:/kobuki'
        compatible_rapps, incompatible_rapps, invalid_rapps = self.indexer.get_compatible_rapps(
            compat)
        print(str(compatible_rapps))
        print(str(incompatible_rapps))
        print(str(invalid_rapps))

        for r in compatible_rapps:
            print(r)

        for r in incompatible_rapps:
            print(r)

        for r in invalid_rapps:
            print(r + " : " + invalid_rapps[r])
        assert_true(
            len(compatible_rapps.keys()) == 1
            and len(incompatible_rapps.keys()) == 1)

        incompat = [
            r for r in compatible_rapps.values() if not r.is_compatible(compat)
        ]
        compat = [
            r for r in incompatible_rapps.values() if r.is_compatible(compat)
        ]
        assert_true(len(incompat) == 0)
        assert_true(len(compat) == 0)
Ejemplo n.º 5
0
def main():
    """
    Main function for the rocon_launch script.
    """
    (args, mappings) = parse_arguments()
    rocon_launch = RoconLaunch(args.terminal_name, args.hold)
    signal.signal(signal.SIGINT, rocon_launch.signal_handler)

    if args.package == '':
        rocon_launcher = roslaunch.rlutil.resolve_launch_arguments(
            args.launcher)[0]
    else:
        rocon_launcher = roslaunch.rlutil.resolve_launch_arguments(
            [args.package] + args.launcher)[0]
    if args.screen:
        roslaunch_options = "--screen"
    else:
        roslaunch_options = ""
    launchers = utils.parse_rocon_launcher(rocon_launcher, roslaunch_options,
                                           mappings)
    for launcher in launchers:
        console.pretty_println(
            "Launching %s on port %s" % (launcher.path, launcher.port),
            console.bold)
        rocon_launch.spawn_roslaunch_window(launcher)
    signal.pause()
Ejemplo n.º 6
0
    def test_get_rapp(self):
        def compare(a, b, field):
            return a.raw_data[field] == b.raw_data[field]

        print_title('Test Get Rapp')
        console.pretty_println('TODO')

        # Basic
        """
Ejemplo n.º 7
0
def console_only_main(node_name='master_info', title='Master Information'):
    # Establishes a connection and prints master information to the console.
    rospy.init_node(node_name)
    master_info = get_master_info()
#     display_available = True if 'DISPLAY' in os.environ.keys() else False

    rocon_console.pretty_println(title, rocon_console.bold)
    print(rocon_console.cyan + "  Name       : " + rocon_console.yellow + master_info.name + rocon_console.reset)
    print(rocon_console.cyan + "  Description: " + rocon_console.yellow + master_info.description + rocon_console.reset)
    print(rocon_console.cyan + "  Icon       : " + rocon_console.yellow + master_info.icon.resource_name + rocon_console.reset)
    print(rocon_console.cyan + "  Version    : " + rocon_console.yellow + master_info.version + rocon_console.reset)
def console_only_main(node_name='concert_service_info',
                      title='Concert Service Information'):
    rospy.init_node(node_name)

    try:
        topic_name = rocon_python_comms.find_topic(
            'concert_msgs/Services',
            timeout=rospy.rostime.Duration(5.0),
            unique=True)
    except rocon_python_comms.NotFoundException as e:
        print(
            rocon_console.red +
            "failed to find unique topic of type 'concert_msgs/Services' [%s]"
            % str(e) + rocon_console.reset)
        sys.exit(1)

    service_info_proxy = rocon_python_comms.SubscriberProxy(
        topic_name, concert_msgs.Services)
    try:
        service_info_proxy.wait_for_publishers()
    except rospy.exceptions.ROSInterruptException:
        rospy.logwarn(
            'Concert Service Info : ros shut down before concert info could be found.'
        )

    trial = 0
    MAX_TRIAL = 5
    while not rospy.is_shutdown():
        result = service_info_proxy(rospy.Duration(0.2))
        if result:
            service_info = result
            break
        rospy.rostime.wallsleep(1.0)  # human time
        trial = trial + 1

        if trial > MAX_TRIAL:
            rospy.logerr(
                'Concert Service info : concert is not found within ' +
                str(MAX_TRIAL) + ' trials')
            sys.exit(1)

    rocon_console.pretty_println('Concert Service Information',
                                 rocon_console.bold)
    for s in service_info.services:
        print_info('  Resource      : ', s.resource_name)
        print_info('  Name          : ', s.name)
        print_info('  Description   : ', s.description)
        print_info('  Author        : ', s.author)
        print_info('  Priority      : ', s.priority)
        print_info('  Launcher Type : ', s.launcher_type)
        print_info('  Status        : ', s.status)
        print_info('  Enabled       : ', s.enabled)
        print ''
Ejemplo n.º 9
0
def load_data(data, verbose=False):
    if verbose:
        console.pretty_println('Loading Test Rapps..', console.bold)
    pwd = os.getcwd()

    loaded = {}
    for name, path in data:
        loaded[name] = Rapp(name, filename=str(pwd + path))

    if verbose:
        for n in loaded:
            console.pretty_println(' - %s' % n)
    return loaded
Ejemplo n.º 10
0
 def test_chatter_configuration(self):
     console.pretty_println(
         "\n*************** Chatter Configuration ************\n",
         console.bold)
     chatter_resources, chatter_clients = setup_chatter_configuration()
     compatibility_tree = compatibility_tree_scheduler.create_compatibility_tree(
         chatter_resources, chatter_clients.values())
     compatibility_tree.print_branches("\nCompatibility Tree\n", '')
     print("")
     pruned_branches = compatibility_tree_scheduler.prune_compatibility_tree(
         compatibility_tree, verbosity=True)
     compatibility_tree_scheduler.print_branches(
         pruned_branches, "\nPruned Compatibility Tree\n", '  ')
     for branch in pruned_branches:
         if branch.name == 'dudette':
             self.assertEquals(1, len(branch.leaves))
             self.assertEquals('dudette', branch.leaves[0].name)
         elif branch.name == 'dude':
             self.assertEquals(2, len(branch.leaves))
Ejemplo n.º 11
0
def test_rapp_classification():
    print_title('Raising on Invalid Rapp Classification')

    console.pretty_println('Virtual Child', console.bold)
    verify_one(InvalidRappException, classify_rapp_type,
               '/test_rapps/rapp/invalid_classification/virtual_child1.rapp')
    verify_one(InvalidRappException, classify_rapp_type,
               '/test_rapps/rapp/invalid_classification/virtual_child2.rapp')

    console.pretty_println('Invalid Virtual Ancestor', console.bold)
    verify_one(
        InvalidRappFieldException, classify_rapp_type,
        '/test_rapps/rapp/invalid_classification/invalid_virtual_ancestor1.rapp'
    )

    console.pretty_println('Invalid Implementation Ancestor', console.bold)
    verify_one(
        InvalidRappFieldException, classify_rapp_type,
        '/test_rapps/rapp/invalid_classification/invalid_implementation_ancestor1.rapp'
    )
    verify_one(
        InvalidRappFieldException, classify_rapp_type,
        '/test_rapps/rapp/invalid_classification/invalid_implementation_ancestor2.rapp'
    )
    verify_one(
        InvalidRappFieldException, classify_rapp_type,
        '/test_rapps/rapp/invalid_classification/invalid_implementation_ancestor3.rapp'
    )
    verify_one(
        InvalidRappFieldException, classify_rapp_type,
        '/test_rapps/rapp/invalid_classification/invalid_implementation_ancestor4.rapp'
    )

    console.pretty_println('Invalid Implementation Child', console.bold)
    verify_one(
        InvalidRappFieldException, classify_rapp_type,
        '/test_rapps/rapp/invalid_classification/invalid_implementation_child1.rapp'
    )

    console.pretty_println('Field Conflict Rapp', console.bold)
    verify_one(InvalidRappFieldException, classify_rapp_type,
               '/test_rapps/rapp/invalid_classification/conflict.rapp')
Ejemplo n.º 12
0
def main(node_name='master_info', title='Master Information'):
    '''
      Establishes a connection and prints master information to the console.
    '''

    rospy.init_node(node_name)
    try:
        topic_name = rocon_python_comms.find_topic('rocon_std_msgs/MasterInfo', timeout=rospy.rostime.Duration(5.0), unique=True)
    except rocon_python_comms.NotFoundException as e:
        print(console.red + "failed to find unique topic of type 'rocon_std_msgs/MasterInfo' [%s]" % str(e) + console.reset)
        sys.exit(1)

    master_info_proxy = rocon_python_comms.SubscriberProxy(topic_name, rocon_std_msgs.MasterInfo)
    try:
        master_info_proxy.wait_for_publishers()
    except rospy.exceptions.ROSInterruptException:
        rospy.logwarn("Concert Info : ros shut down before concert info could be found.")

    master_info = rocon_std_msgs.MasterInfo()
    while not rospy.is_shutdown():
        result = master_info_proxy(rospy.Duration(0.2))
        if result:
            master_info = result
            break
        rospy.rostime.wallsleep(1.0)  # human time

    console.pretty_println(title, console.bold)
    print(console.cyan + "  Name       : " + console.yellow + master_info.name + console.reset)
    print(console.cyan + "  Description: " + console.yellow + master_info.description + console.reset)
    print(console.cyan + "  Icon       : " + console.yellow + master_info.icon.resource_name + console.reset)
    print(console.cyan + "  Version    : " + console.yellow + master_info.version + console.reset)

    if qt_available:
        icon = rocon_python_utils.ros.find_resource_from_string(master_info.icon.resource_name)
        signal.signal(signal.SIGINT, signal.SIG_DFL)  # make sure this comes after the rospy call, otherwise it will handle signals.
        app = QtGui.QApplication(sys.argv)
        window = Window(master_info.name, master_info.description, master_info.version, icon)
        window.show()
        sys.exit(app.exec_())
def _prune_resolvable_branches(compatibility_tree, verbosity):
    '''
      todo.

      :param compatibility_tree: branches listing compatible resource - clients relationships
      :type compatibility_tree: :class:`.CompatibilityTree`
      :param bool verbosity: adds some pretty printed output to screen for debugging.
      :returns: the pruned branches and the trimmed tree
      :rtype: ([:class:`.CompatibilityBranch`], :class:`.CompatibilityTree`)
    '''
    if verbosity:
        #print("")
        compatibility_tree.print_branches("Pruning Resolvable Branches",
                                          "    ")
    pruned_branches = []
    remaining_branches = []
    branches = compatibility_tree.branches
    removed_leaves = []
    # look for a branch that can't be worked on any more - i.e. is either
    # empty, or has only one leaf
    for branch in branches:
        if not branch.leaves:
            if not pruned_branches:  # Only accept one change at a time
                pruned_branches.append(branch)
            else:
                remaining_branches.append(branch)
        elif len(branch.leaves) == 1:
            if not pruned_branches:
                pruned_branches.append(branch)
                removed_leaves.extend(branch.leaves)
            else:
                remaining_branches.append(branch)
        else:
            remaining_branches.append(branch)
    removed_leaves = list(set(removed_leaves))  # get a unique list
    for branch in remaining_branches:
        branch.prune_leaves(removed_leaves)
    # are we guaranteed of clearing all of these?
    # if there was an update and there is work left to do, dig down
    if pruned_branches:
        if remaining_branches:
            if verbosity:
                console.pretty_println(
                    "      --> pruned leaves: %s" %
                    ' '.join([leaf.name for leaf in removed_leaves]),
                    console.green)
                console.pretty_println(
                    "      --> pruned resolvable branches: %s" %
                    ' '.join([branch.name for branch in pruned_branches]),
                    console.green)
            return pruned_branches, CompatibilityTree(
                branches=remaining_branches)
        else:
            return pruned_branches, None
    else:
        if verbosity:
            console.pretty_println("      --> no resolvable branches",
                                   console.green)
        return [], None
Ejemplo n.º 14
0
def test_rapp_inheritance():
    def inherit_pair(path):
        filename = pwd + path + '/child.rapp'
        child = Rapp('child', filename=filename)
        filename = pwd + path + '/parent.rapp'
        parent = Rapp('parent', filename=filename)

        child.inherit(parent)
        return child

    def validate(data, valid_data):
        for f, d in valid_data:
            if not f in data:
                return False
            if not data[f] == d:
                return False
        return True

    print_title('Rapp Inheritance')

    # full inheritance
    path = '/test_rapps/rapp/inherity/full'
    console.pretty_println(' - %s' % path)
    child = inherit_pair(path)

    d = [('display', 'Talker'),
         ('description', 'Default ros style talker tutorial'),
         ('public_interface', 'rocon_apps/talker.interface'),
         ('public_parameters', 'rocon_apps/talker.parameters'),
         ('icon', 'rocon_apps/talker.png')]
    assert_true(validate(child.raw_data, d))

    # icon and publics
    path = '/test_rapps/rapp/inherity/icon_and_publics'
    console.pretty_println(' - %s' % path)
    child = inherit_pair(path)
    d = [('display', 'Child Talker in icon and publics'),
         ('description', 'Hola Child...........'),
         ('public_interface', 'rocon_apps/talker.interface'),
         ('public_parameters', 'rocon_apps/talker.parameters'),
         ('icon', 'rocon_apps/talker.png')]
    assert_true(validate(child.raw_data, d))

    # publics
    path = '/test_rapps/rapp/inherity/publics'
    console.pretty_println(' - %s' % path)
    child = inherit_pair(path)
    d = [('display', 'Child Talker in publics'),
         ('description', 'public public child'),
         ('public_interface', 'rocon_apps/talker.interface'),
         ('public_parameters', 'rocon_apps/talker.parameters')]
    assert_true(validate(child.raw_data, d))

    # from meta
    path = '/test_rapps/rapp/inherity/from_meta'
    console.pretty_println(' - %s' % path)
    child = inherit_pair(path)
    d = [('display', 'Talker in from meta'),
         ('description', 'from meta meta meta metaaaaaaaaa'),
         ('public_interface', 'rocon_apps/talker.interface'),
         ('public_parameters', 'rocon_apps/talker.parameters'),
         ('icon', 'rocon_apps/talker.png')]
    assert_true(validate(child.raw_data, d))

    # from another child
    path = '/test_rapps/rapp/inherity/from_child'
    console.pretty_println(' - %s' % path)
    child = inherit_pair(path)
    d = [('display', 'Talker'),
         ('description', 'Default ros style talker tutorial'),
         ('icon', 'rocon_apps/talker.png')]
    assert_true(validate(child.raw_data, d))
Ejemplo n.º 15
0
def verify_one(Except, func, filename):
    f = pwd + filename
    (yaml_data, app_data) = load_rapp_yaml_from_file(f)
    console.pretty_println(' - %s' % f)
    assert_raises(Except, func, app_data)
Ejemplo n.º 16
0
    def test_to_dot(self):
        print_title('Test To Dot')

        console.pretty_println('Not Implemented')
        assert_raises(NotImplementedError, self.indexer.to_dot)
 def print_branches(self, name='Branches', indent=''):
     console.pretty_println(indent + "%s" % name, console.bold)
     for branch in self.branches:
         print(indent + "  %s" % branch)
Ejemplo n.º 18
0
def main():
    global processes
    global roslaunch_pids
    signal.signal(signal.SIGINT, signal_handler)
    args = parse_arguments()
    terminal = None
    if not args.no_terminals:
        if not rocon_python_utils.system.which(
                'konsole') and not rocon_python_utils.system.which(
                    'gnome-terminal') and not rocon_python_utils.system.which(
                        'x-terminal-emulator'):
            console.error(
                "Cannot find a suitable terminal [x-terminal-emulator, konsole, gnome-termional]"
            )
            sys.exit(1)
        terminal = choose_terminal(args.gnome, args.konsole)

    if args.package == '':
        rocon_launcher = roslaunch.rlutil.resolve_launch_arguments(
            args.launcher)[0]
    else:
        rocon_launcher = roslaunch.rlutil.resolve_launch_arguments(
            [args.package] + args.launcher)[0]
    if args.screen:
        roslaunch_options = "--screen"
    else:
        roslaunch_options = ""
    launchers = parse_rocon_launcher(rocon_launcher, roslaunch_options)
    temporary_launchers = []
    for launcher in launchers:
        console.pretty_println(
            "Launching [%s, %s] on port %s" %
            (launcher['package'], launcher['name'], launcher['port']),
            console.bold)
        ##########################
        # Customise the launcher
        ##########################
        temp = tempfile.NamedTemporaryFile(mode='w+t', delete=False)
        print("Launching %s" % temp.name)
        launcher_filename = rocon_python_utils.ros.find_resource(
            launcher['package'], launcher['name'])
        launch_text = '<launch>\n'
        if args.screen:
            launch_text += '  <param name="rocon/screen" value="true"/>\n'
        else:
            launch_text += '  <param name="rocon/screen" value="false"/>\n'
        launch_text += '  <include file="%s">\n' % launcher_filename
        for (arg_name, arg_value) in launcher['args']:
            launch_text += '    <arg name="%s" value="%s"/>\n' % (arg_name,
                                                                  arg_value)
        launch_text += '  </include>\n'
        launch_text += '</launch>\n'
        #print launch_text
        temp.write(launch_text)
        temp.close()  # unlink it later
        temporary_launchers.append(temp)
        ##########################
        # Start the terminal
        ##########################
        if terminal == 'konsole':
            p = subprocess.Popen([
                terminal, '-p',
                'tabtitle=%s' % launcher['title'], '--nofork', '--hold', '-e',
                "/bin/bash", "-c",
                "roslaunch %s --disable-title --port %s %s" %
                (launcher['options'], launcher['port'], temp.name)
            ],
                                 preexec_fn=preexec)
        elif terminal == 'gnome-terminal.wrapper' or terminal == 'gnome-terminal':
            # --disable-factory inherits the current environment, bit wierd.
            cmd = [
                'gnome-terminal',
                '--title=%s' % launcher['title'], '--disable-factory', "-e",
                "/bin/bash -c 'roslaunch %s --disable-title --port %s %s';/bin/bash"
                % (launcher['options'], launcher['port'], temp.name)
            ]
            p = subprocess.Popen(cmd, preexec_fn=preexec)
        else:
            cmd = ["roslaunch"]
            if launcher['options']:
                cmd.append(launcher['options'])
            cmd.extend(["--port", launcher['port'], temp.name])
            p = subprocess.Popen(cmd, preexec_fn=preexec)
        processes.append(p)
    signal.pause()
    # Have to unlink them here rather than in the for loop above, because the whole gnome-terminal
    # subprocess takes a while to kick in (in the background) and the unlinking may occur before
    # it actually runs the roslaunch that needs the file.
    for temporary_launcher in temporary_launchers:
        os.unlink(temporary_launcher.name)
Ejemplo n.º 19
0
def test_rapp_inheritance():
    def inherit_pair(path):
        filename = pwd + path + '/child.rapp'
        child = Rapp('child', filename=filename)
        filename = pwd + path + '/parent.rapp'
        parent = Rapp('parent', filename=filename)

        child.inherit(parent)
        return child

    def validate(data, valid_data):
        for f, d in valid_data:
            if not f in data:
                return False
            if not data[f] == d:
                return False
        return True

    print_title('Rapp Inheritance')

    # full inheritance
    path = '/test_rapps/rapp/inherity/full'
    icon_path = os.path.join(os.getcwd(), "test_rapps", "rapp", "inherity",
                             "foo.png")
    console.pretty_println(' - %s' % path)
    child = inherit_pair(path)

    d = [('display', 'Talker'),
         ('description', 'Default ros style talker tutorial'),
         ('public_interface', {
             'services': [],
             'publishers': [{
                 'type': 'std_msgs/String',
                 'name': 'chatter'
             }],
             'action_clients': [],
             'subscribers': [],
             'action_servers': []
         }), ('public_parameters', {
             'message': 'hello world',
             'frequency': 10
         }), ('icon', icon_path)]
    assert_true(validate(child.raw_data, d))

    # icon and publics
    path = '/test_rapps/rapp/inherity/icon_and_publics'
    console.pretty_println(' - %s' % path)
    child = inherit_pair(path)
    d = [('display', 'Child Talker in icon and publics'),
         ('description', 'Hola Child...........'),
         ('public_interface', {
             'services': [],
             'publishers': [{
                 'type': 'std_msgs/String',
                 'name': 'chatter'
             }],
             'action_clients': [],
             'subscribers': [],
             'action_servers': []
         }), ('public_parameters', {
             'message': 'hello world',
             'frequency': 10
         }), ('icon', icon_path)]
    assert_true(validate(child.raw_data, d))

    # publics
    path = '/test_rapps/rapp/inherity/publics'
    console.pretty_println(' - %s' % path)
    child = inherit_pair(path)
    d = [('display', 'Child Talker in publics'),
         ('description', 'public public child'),
         ('public_interface', {
             'services': [],
             'publishers': [{
                 'type': 'std_msgs/String',
                 'name': 'chatter'
             }],
             'action_clients': [],
             'subscribers': [],
             'action_servers': []
         }), ('public_parameters', {
             'message': 'hello world',
             'frequency': 10
         })]
    assert_true(validate(child.raw_data, d))

    # from meta
    path = '/test_rapps/rapp/inherity/from_meta'
    console.pretty_println(' - %s' % path)
    child = inherit_pair(path)
    print("Child: %s" % child.raw_data)
    d = [('display', 'Talker in from meta'),
         ('description', 'from meta meta meta metaaaaaaaaa'),
         ('public_interface', {
             'services': [],
             'publishers': [{
                 'type': 'std_msgs/String',
                 'name': 'chatter'
             }],
             'action_clients': [],
             'subscribers': [],
             'action_servers': []
         }), ('public_parameters', {
             'message': 'hello world',
             'frequency': 10
         }), ('icon', icon_path)]
    assert_true(validate(child.raw_data, d))

    # from another child
    path = '/test_rapps/rapp/inherity/from_child'
    console.pretty_println(' - %s' % path)
    child = inherit_pair(path)
    d = [('display', 'Talker'),
         ('description', 'Default ros style talker tutorial'),
         ('icon', icon_path)]
    assert_true(validate(child.raw_data, d))
def _prune_least_valuable_leaf(compatibility_tree, verbosity):
    '''
       This should be only called on a tree with branches
       that have redundancy, i.e. more potential clients than
       required.

       Does this by scanning the subtree looking for the least
       visible leaf (client) and pruning it from all branches except
       the thinnest branch it exists on (fewest other leaves).
       This branch is typically the most sensitive to variations so we
       attack it first.

       :param compatibility_tree:
       :type compatibility_tree: :class:`.CompatibilityTree`
       :returns: the pruned compatibility_tree:
       :rtype: :class:`.CompatibilityTree`
   '''
    if verbosity:
        compatibility_tree.print_branches('Pruning Least Valuable Leaf',
                                          '    ')
    leaf_count = {}  # client name - integer count
    leaves = {}
    thinnest_branch_leaf_count = {}
    # Make a database of all the leaves in all the branches and count how many
    # times a leaf shows up and which branch is it's most sensitive.
    for branch in compatibility_tree.branches:
        for leaf in branch.leaves:
            if leaf.name not in leaves:
                leaves[leaf.name] = leaf
                thinnest_branch_leaf_count[leaf.name] = len(branch.leaves)
                leaf_count[
                    leaf.
                    name] = 1 if not leaf.allocated else 100  # weight in favour of unallocated clients
            else:
                leaf_count[leaf.name] = leaf_count[leaf.name] + 1
            if len(branch.leaves) < thinnest_branch_leaf_count[leaf.name]:
                thinnest_branch_leaf_count[leaf.name] = len(branch.leaves)
    # Now find the least visible leaf, that's the one we have to lock down first.
    least_visible_count = min(leaf_count.values())
    least_visible_leaf_names = [
        name for name in leaf_count if leaf_count[name] == least_visible_count
    ]
    least_visible_leaf = None
    for leaf in least_visible_leaf_names:
        if least_visible_leaf is None or thinnest_branch_leaf_count[
                leaf] < thinnest_branch_leaf_count[least_visible_leaf.name]:
            least_visible_leaf = leaves[leaf]
    # Lock down the thinnest branch that leaf shows up on.
    pruned_compatibility_tree = compatibility_tree
    for branch in pruned_compatibility_tree.branches:
        if least_visible_leaf.name in [leaf.name for leaf in branch.leaves]:
            if len(branch.leaves) == thinnest_branch_leaf_count[
                    least_visible_leaf.name]:
                branch.leaves = [least_visible_leaf]
                break
    # And prune it from elsewhere
    for branch in pruned_compatibility_tree.branches:
        if len(branch.leaves) != 1:
            branch.prune_leaves([least_visible_leaf])
    if verbosity:
        console.pretty_println(
            "      --> pruning least visible leaf: %s" %
            least_visible_leaf.name, console.green)
    return pruned_compatibility_tree
def print_leaves(leaves, name='Leaves', indent=''):
    console.pretty_println(indent + "%s" % name, console.bold)
    console.pretty_print(indent + "  Clients: ", console.cyan)
    console.pretty_println("%s " % [leaf.name for leaf in leaves],
                           console.yellow)