def test_simple_distance3(self): edges = [('ant', 'bat'), ('bat', 'cat'), ('cat', 'dog'), ('dog', 'ant'), ('dog', 'eel')] for utf8 in (True, False): for colour in sorted(graph.COLOUR_SETS): print('utf8 %s, colour %s' % (utf8, colour)) s = graph.distance_matrix(None, edges, utf8=utf8, colour=colour) print(s) print()
def run(self, H=None, output=None, shorten_names=False, key=True, talk_to_remote=False, sambaopts=None, credopts=None, versionopts=None, color=None, color_scheme=None, utf8=None, format=None, importldif=None, xdot=False): lp = sambaopts.get_loadparm() if importldif is None: creds = credopts.get_credentials(lp, fallback_machine=True) else: creds = None H = self.import_ldif_db(importldif, lp) local_kcc, dsas = self.get_kcc_and_dsas(H, lp, creds) local_dsa_dn = local_kcc.my_dsa_dnstr.split(',', 1)[1] vertices = set() attested_edges = [] for dsa_dn in dsas: if talk_to_remote: res = local_kcc.samdb.search(dsa_dn, scope=SCOPE_BASE, attrs=["dNSHostName"]) dns_name = res[0]["dNSHostName"][0] try: samdb = self.get_db("ldap://%s" % dns_name, sambaopts, credopts) except LdbError as e: print("Could not contact ldap://%s (%s)" % (dns_name, e), file=sys.stderr) continue ntds_dn = samdb.get_dsServiceName() dn = samdb.domain_dn() else: samdb = self.get_db(H, sambaopts, credopts) ntds_dn = 'CN=NTDS Settings,' + dsa_dn dn = dsa_dn res = samdb.search(ntds_dn, scope=SCOPE_BASE, attrs=["msDS-isRODC"]) is_rodc = res[0]["msDS-isRODC"][0] == 'TRUE' vertices.add((ntds_dn, 'RODC' if is_rodc else '')) # XXX we could also look at schedule res = samdb.search(dn, scope=SCOPE_SUBTREE, expression="(objectClass=nTDSConnection)", attrs=['fromServer'], # XXX can't be critical for ldif test #controls=["search_options:1:2"], controls=["search_options:0:2"], ) for msg in res: msgdn = str(msg.dn) dest_dn = msgdn[msgdn.index(',') + 1:] attested_edges.append((msg['fromServer'][0], dest_dn, ntds_dn)) if importldif and H == self._tmp_fn_to_delete: os.remove(H) os.rmdir(os.path.dirname(H)) # now we overlay all the graphs and generate styles accordingly edges = {} for src, dest, attester in attested_edges: k = (src, dest) if k in edges: e = edges[k] else: e = NTDSConn(*k) edges[k] = e e.attest(attester) vertices, rodc_status = zip(*sorted(vertices)) if self.calc_output_format(format, output) == 'distance': color_scheme = self.calc_distance_color_scheme(color, color_scheme, output) colours = COLOUR_SETS[color_scheme] c_header = colours.get('header', '') c_reset = colours.get('reset', '') epilog = [] if 'RODC' in rodc_status: epilog.append('No outbound connections are expected from RODCs') if not talk_to_remote: # If we are not talking to remote servers, we list all # the connections. graph_edges = edges.keys() title = 'NTDS Connections known to %s' % local_dsa_dn else: # If we are talking to the remotes, there are # interesting cases we can discover. What matters most # is that the destination (i.e. owner) knowns about # the connection, but it would be worth noting if the # source doesn't. Another strange situation could be # when a DC thinks there is a connection elsewhere, # but the computers allegedly involved don't believe # it exists. # # With limited bandwidth in the table, we mark the # edges known to the destination, and note the other # cases in a list after the diagram. graph_edges = [] source_denies = [] dest_denies = [] both_deny = [] for e, conn in edges.items(): if conn.dest_attests: graph_edges.append(e) if not conn.src_attests: source_denies.append(e) elif conn.src_attests: dest_denies.append(e) else: both_deny.append(e) title = 'NTDS Connections known to each destination DC' if both_deny: epilog.append('The following connections are alleged by ' 'DCs other than the source and ' 'destination:\n') for e in both_deny: epilog.append(' %s -> %s\n' % e) if dest_denies: epilog.append('The following connections are alleged by ' 'DCs other than the destination but ' 'including the source:\n') for e in dest_denies: epilog.append(' %s -> %s\n' % e) if source_denies: epilog.append('The following connections ' '(included in the chart) ' 'are not known to the source DC:\n') for e in source_denies: epilog.append(' %s -> %s\n' % e) s = distance_matrix(vertices, graph_edges, utf8=utf8, colour=color_scheme, shorten_names=shorten_names, generate_key=key, grouping_function=get_dnstr_site, row_comments=rodc_status) epilog = ''.join(epilog) if epilog: epilog = '\n%sNOTES%s\n%s' % (c_header, c_reset, epilog) self.write('\n%s\n\n%s\n%s' % (title, s, epilog), output) return dot_edges = [] edge_colours = [] edge_styles = [] edge_labels = [] n_servers = len(dsas) for k, e in sorted(edges.items()): dot_edges.append(k) if e.observations == n_servers or not talk_to_remote: edge_colours.append('#000000') edge_styles.append('') elif e.dest_attests: edge_styles.append('') if e.src_attests: edge_colours.append('#0000ff') else: edge_colours.append('#cc00ff') elif e.src_attests: edge_colours.append('#ff0000') edge_styles.append('style=dashed') else: edge_colours.append('#ff0000') edge_styles.append('style=dotted') key_items = [] if key: key_items.append((False, 'color="#000000"', "NTDS Connection")) for colour, desc in (('#0000ff', "missing from some DCs"), ('#cc00ff', "missing from source DC")): if colour in edge_colours: key_items.append((False, 'color="%s"' % colour, desc)) for style, desc in (('style=dashed', "unknown to destination"), ('style=dotted', "unknown to source and destination")): if style in edge_styles: key_items.append((False, 'color="#ff0000; %s"' % style, desc)) if talk_to_remote: title = 'NTDS Connections' else: title = 'NTDS Connections known to %s' % local_dsa_dn s = dot_graph(sorted(vertices), dot_edges, directed=True, title=title, edge_colors=edge_colours, edge_labels=edge_labels, edge_styles=edge_styles, shorten_names=shorten_names, key_items=key_items) if format == 'xdot': self.call_xdot(s, output) else: self.write(s, output)
def run(self, H=None, output=None, shorten_names=False, key=True, talk_to_remote=False, sambaopts=None, credopts=None, versionopts=None, mode='self', partition=None, color=None, color_scheme=None, utf8=None, format=None, xdot=False): # We use the KCC libraries in readonly mode to get the # replication graph. lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp, fallback_machine=True) local_kcc, dsas = self.get_kcc_and_dsas(H, lp, creds) unix_now = local_kcc.unix_now # Allow people to say "--partition=DOMAIN" rather than # "--partition=DC=blah,DC=..." short_partitions, long_partitions = get_partition_maps(local_kcc.samdb) if partition is not None: partition = short_partitions.get(partition.upper(), partition) if partition not in long_partitions: raise CommandError("unknown partition %s" % partition) # nc_reps is an autovivifying dictionary of dictionaries of lists. # nc_reps[partition]['current' | 'needed'] is a list of # (dsa dn string, repsFromTo object) pairs. nc_reps = defaultdict(lambda: defaultdict(list)) guid_to_dnstr = {} # We run a new KCC for each DSA even if we aren't talking to # the remote, because after kcc.run (or kcc.list_dsas) the kcc # ends up in a messy state. for dsa_dn in dsas: kcc = KCC(unix_now, readonly=True) if talk_to_remote: res = local_kcc.samdb.search(dsa_dn, scope=SCOPE_BASE, attrs=["dNSHostName"]) dns_name = res[0]["dNSHostName"][0] print("Attempting to contact ldap://%s (%s)" % (dns_name, dsa_dn), file=sys.stderr) try: kcc.load_samdb("ldap://%s" % dns_name, lp, creds) except KCCError as e: print("Could not contact ldap://%s (%s)" % (dns_name, e), file=sys.stderr) continue kcc.run(H, lp, creds) else: kcc.load_samdb(H, lp, creds) kcc.run(H, lp, creds, forced_local_dsa=dsa_dn) dsas_from_here = set(kcc.list_dsas()) if dsas != dsas_from_here: print("found extra DSAs:", file=sys.stderr) for dsa in (dsas_from_here - dsas): print(" %s" % dsa, file=sys.stderr) print("missing DSAs (known locally, not by %s):" % dsa_dn, file=sys.stderr) for dsa in (dsas - dsas_from_here): print(" %s" % dsa, file=sys.stderr) for remote_dn in dsas_from_here: if mode == 'others' and remote_dn == dsa_dn: continue elif mode == 'self' and remote_dn != dsa_dn: continue remote_dsa = kcc.get_dsa('CN=NTDS Settings,' + remote_dn) kcc.translate_ntdsconn(remote_dsa) guid_to_dnstr[str(remote_dsa.dsa_guid)] = remote_dn # get_reps_tables() returns two dictionaries mapping # dns to NCReplica objects c, n = remote_dsa.get_rep_tables() for part, rep in c.items(): if partition is None or part == partition: nc_reps[part]['current'].append((dsa_dn, rep)) for part, rep in n.items(): if partition is None or part == partition: nc_reps[part]['needed'].append((dsa_dn, rep)) all_edges = {'needed': {'to': [], 'from': []}, 'current': {'to': [], 'from': []}} for partname, part in nc_reps.items(): for state, edgelists in all_edges.items(): for dsa_dn, rep in part[state]: short_name = long_partitions.get(partname, partname) for r in rep.rep_repsFrom: edgelists['from'].append( (dsa_dn, guid_to_dnstr[str(r.source_dsa_obj_guid)], short_name)) for r in rep.rep_repsTo: edgelists['to'].append( (guid_to_dnstr[str(r.source_dsa_obj_guid)], dsa_dn, short_name)) # Here we have the set of edges. From now it is a matter of # interpretation and presentation. if self.calc_output_format(format, output) == 'distance': color_scheme = self.calc_distance_color_scheme(color, color_scheme, output) header_strings = { 'from': "RepsFrom objects for %s", 'to': "RepsTo objects for %s", } for state, edgelists in all_edges.items(): for direction, items in edgelists.items(): part_edges = defaultdict(list) for src, dest, part in items: part_edges[part].append((src, dest)) for part, edges in part_edges.items(): s = distance_matrix(None, edges, utf8=utf8, colour=color_scheme, shorten_names=shorten_names, generate_key=key, grouping_function=get_dnstr_site) s = "\n%s\n%s" % (header_strings[direction] % part, s) self.write(s, output) return edge_colours = [] edge_styles = [] dot_edges = [] dot_vertices = set() used_colours = {} key_set = set() for state, edgelist in all_edges.items(): for direction, items in edgelist.items(): for src, dest, part in items: colour = used_colours.setdefault((part), colour_hash((part, direction))) linestyle = 'dotted' if state == 'needed' else 'solid' arrow = 'open' if direction == 'to' else 'empty' dot_vertices.add(src) dot_vertices.add(dest) dot_edges.append((src, dest)) edge_colours.append(colour) style = 'style="%s"; arrowhead=%s' % (linestyle, arrow) edge_styles.append(style) key_set.add((part, 'reps' + direction.title(), colour, style)) key_items = [] if key: for part, direction, colour, linestyle in sorted(key_set): key_items.append((False, 'color="%s"; %s' % (colour, linestyle), "%s %s" % (part, direction))) key_items.append((False, 'style="dotted"; arrowhead="open"', "repsFromTo is needed")) key_items.append((False, 'style="solid"; arrowhead="open"', "repsFromTo currently exists")) s = dot_graph(dot_vertices, dot_edges, directed=True, edge_colors=edge_colours, edge_styles=edge_styles, shorten_names=shorten_names, key_items=key_items) if format == 'xdot': self.call_xdot(s, output) else: self.write(s, output)
def test_simple_distance3(self): edges = [('ant', 'bat'), ('bat', 'cat'), ('cat', 'dog'), ('dog', 'ant'), ('dog', 'eel')] expected = { 'utf8 True, colour None': ''' destination â•â”€â”€â”€â”€â”€ ant │â•â”€â”€â”€â”€ bat ││â•â”€â”€â”€ cat │││â•â”€â”€ dog source ││││â•â”€ eel ant ·1234 bat 3·123 cat 23·12 dog 123·1 eel ----· ''', 'utf8 True, colour ansi': ''' [4mdestination[0m [0m[37mâ•â”€â”€â”€â”€â”€ ant[0m [37m│[0m[1;30mâ•â”€â”€â”€â”€ bat[0m [37m│[1;30m│[0m[37mâ•â”€â”€â”€ cat[0m [37m│[1;30m│[37m│[0m[1;30mâ•â”€â”€ dog[0m [4msource[0m [37m│[1;30m│[37m│[1;30m│[0m[37mâ•â”€ eel[0m [37m ant[0m [0m[37m·[0m[1;32m1[0m[33m2[0m[33m3[0m[33m4[0m[0m [1;30m bat[0m [33m3[0m[0m[1;30m·[0m[1;32m1[0m[33m2[0m[33m3[0m[0m [37m cat[0m [33m2[0m[33m3[0m[0m[37m·[0m[1;32m1[0m[33m2[0m[0m [1;30m dog[0m [1;32m1[0m[33m2[0m[33m3[0m[0m[1;30m·[0m[1;32m1[0m[0m [37m eel[0m [1;31m-[1;31m-[1;31m-[1;31m-[0m[37m·[0m[0m ''', 'utf8 True, colour ansi-heatmap': ''' [4mdestination[0m [0m[37mâ•â”€â”€â”€â”€â”€ ant[0m [37m│[0m[1;30mâ•â”€â”€â”€â”€ bat[0m [37m│[1;30m│[0m[37mâ•â”€â”€â”€ cat[0m [37m│[1;30m│[37m│[0m[1;30mâ•â”€â”€ dog[0m [4msource[0m [37m│[1;30m│[37m│[1;30m│[0m[37mâ•â”€ eel[0m [37m ant[0m [0m[37m·[0m[1;42m1[0m[43m2[0m[43m3[0m[43m4[0m[0m [1;30m bat[0m [43m3[0m[0m[1;30m·[0m[1;42m1[0m[43m2[0m[43m3[0m[0m [37m cat[0m [43m2[0m[43m3[0m[0m[37m·[0m[1;42m1[0m[43m2[0m[0m [1;30m dog[0m [1;42m1[0m[43m2[0m[43m3[0m[0m[1;30m·[0m[1;42m1[0m[0m [37m eel[0m [1;41m-[1;41m-[1;41m-[1;41m-[0m[37m·[0m[0m ''', 'utf8 True, colour xterm-256color': ''' [4mdestination[0m [0m[38;5;39mâ•â”€â”€â”€â”€â”€ ant[0m [38;5;39m│[0m[38;5;45mâ•â”€â”€â”€â”€ bat[0m [38;5;39m│[38;5;45m│[0m[38;5;39mâ•â”€â”€â”€ cat[0m [38;5;39m│[38;5;45m│[38;5;39m│[0m[38;5;45mâ•â”€â”€ dog[0m [4msource[0m [38;5;39m│[38;5;45m│[38;5;39m│[38;5;45m│[0m[38;5;39mâ•â”€ eel[0m [38;5;39m ant[0m [0m[38;5;39m·[0m[38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[38;5;208m4[0m[0m [38;5;45m bat[0m [38;5;208m3[0m[0m[38;5;45m·[0m[38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[0m [38;5;39m cat[0m [38;5;214m2[0m[38;5;208m3[0m[0m[38;5;39m·[0m[38;5;112m1[0m[38;5;214m2[0m[0m [38;5;45m dog[0m [38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[0m[38;5;45m·[0m[38;5;112m1[0m[0m [38;5;39m eel[0m [48;5;124m-[48;5;124m-[48;5;124m-[48;5;124m-[0m[38;5;39m·[0m[0m ''', 'utf8 True, colour xterm-256color-heatmap': ''' [4mdestination[0m [0m[38;5;171mâ•â”€â”€â”€â”€â”€ ant[0m [38;5;171m│[0m[38;5;207mâ•â”€â”€â”€â”€ bat[0m [38;5;171m│[38;5;207m│[0m[38;5;171mâ•â”€â”€â”€ cat[0m [38;5;171m│[38;5;207m│[38;5;171m│[0m[38;5;207mâ•â”€â”€ dog[0m [4msource[0m [38;5;171m│[38;5;207m│[38;5;171m│[38;5;207m│[0m[38;5;171mâ•â”€ eel[0m [38;5;171m ant[0m [0m[38;5;171m·[0m[48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[48;5;208m4[0m[0m [38;5;207m bat[0m [48;5;208m3[0m[0m[38;5;207m·[0m[48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[0m [38;5;171m cat[0m [48;5;214m2[0m[48;5;208m3[0m[0m[38;5;171m·[0m[48;5;112m1[0m[48;5;214m2[0m[0m [38;5;207m dog[0m [48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[0m[38;5;207m·[0m[48;5;112m1[0m[0m [38;5;171m eel[0m [48;5;124m-[48;5;124m-[48;5;124m-[48;5;124m-[0m[38;5;171m·[0m[0m ''', 'utf8 False, colour None': ''' destination ,----- ant |,---- bat ||,--- cat |||,-- dog source ||||,- eel ant 01234 bat 30123 cat 23012 dog 12301 eel ----0 ''', 'utf8 False, colour ansi': ''' [4mdestination[0m [0m[37m,----- ant[0m [37m|[0m[1;30m,---- bat[0m [37m|[1;30m|[0m[37m,--- cat[0m [37m|[1;30m|[37m|[0m[1;30m,-- dog[0m [4msource[0m [37m|[1;30m|[37m|[1;30m|[0m[37m,- eel[0m [37m ant[0m [0m[37m0[0m[1;32m1[0m[33m2[0m[33m3[0m[33m4[0m[0m [1;30m bat[0m [33m3[0m[0m[1;30m0[0m[1;32m1[0m[33m2[0m[33m3[0m[0m [37m cat[0m [33m2[0m[33m3[0m[0m[37m0[0m[1;32m1[0m[33m2[0m[0m [1;30m dog[0m [1;32m1[0m[33m2[0m[33m3[0m[0m[1;30m0[0m[1;32m1[0m[0m [37m eel[0m [1;31m-[1;31m-[1;31m-[1;31m-[0m[37m0[0m[0m ''', 'utf8 False, colour ansi-heatmap': ''' [4mdestination[0m [0m[37m,----- ant[0m [37m|[0m[1;30m,---- bat[0m [37m|[1;30m|[0m[37m,--- cat[0m [37m|[1;30m|[37m|[0m[1;30m,-- dog[0m [4msource[0m [37m|[1;30m|[37m|[1;30m|[0m[37m,- eel[0m [37m ant[0m [0m[37m0[0m[1;42m1[0m[43m2[0m[43m3[0m[43m4[0m[0m [1;30m bat[0m [43m3[0m[0m[1;30m0[0m[1;42m1[0m[43m2[0m[43m3[0m[0m [37m cat[0m [43m2[0m[43m3[0m[0m[37m0[0m[1;42m1[0m[43m2[0m[0m [1;30m dog[0m [1;42m1[0m[43m2[0m[43m3[0m[0m[1;30m0[0m[1;42m1[0m[0m [37m eel[0m [1;41m-[1;41m-[1;41m-[1;41m-[0m[37m0[0m[0m ''', 'utf8 False, colour xterm-256color': ''' [4mdestination[0m [0m[38;5;39m,----- ant[0m [38;5;39m|[0m[38;5;45m,---- bat[0m [38;5;39m|[38;5;45m|[0m[38;5;39m,--- cat[0m [38;5;39m|[38;5;45m|[38;5;39m|[0m[38;5;45m,-- dog[0m [4msource[0m [38;5;39m|[38;5;45m|[38;5;39m|[38;5;45m|[0m[38;5;39m,- eel[0m [38;5;39m ant[0m [0m[38;5;39m0[0m[38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[38;5;208m4[0m[0m [38;5;45m bat[0m [38;5;208m3[0m[0m[38;5;45m0[0m[38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[0m [38;5;39m cat[0m [38;5;214m2[0m[38;5;208m3[0m[0m[38;5;39m0[0m[38;5;112m1[0m[38;5;214m2[0m[0m [38;5;45m dog[0m [38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[0m[38;5;45m0[0m[38;5;112m1[0m[0m [38;5;39m eel[0m [48;5;124m-[48;5;124m-[48;5;124m-[48;5;124m-[0m[38;5;39m0[0m[0m ''', 'utf8 False, colour xterm-256color-heatmap': ''' [4mdestination[0m [0m[38;5;171m,----- ant[0m [38;5;171m|[0m[38;5;207m,---- bat[0m [38;5;171m|[38;5;207m|[0m[38;5;171m,--- cat[0m [38;5;171m|[38;5;207m|[38;5;171m|[0m[38;5;207m,-- dog[0m [4msource[0m [38;5;171m|[38;5;207m|[38;5;171m|[38;5;207m|[0m[38;5;171m,- eel[0m [38;5;171m ant[0m [0m[38;5;171m0[0m[48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[48;5;208m4[0m[0m [38;5;207m bat[0m [48;5;208m3[0m[0m[38;5;207m0[0m[48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[0m [38;5;171m cat[0m [48;5;214m2[0m[48;5;208m3[0m[0m[38;5;171m0[0m[48;5;112m1[0m[48;5;214m2[0m[0m [38;5;207m dog[0m [48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[0m[38;5;207m0[0m[48;5;112m1[0m[0m [38;5;171m eel[0m [48;5;124m-[48;5;124m-[48;5;124m-[48;5;124m-[0m[38;5;171m0[0m[0m ''' } for utf8 in (True, False): for colour in self.sorted_colour_sets: k = 'utf8 %s, colour %s' % (utf8, colour) s = graph.distance_matrix(None, edges, utf8=utf8, colour=colour) self.assertStringsEqual(s, expected[k], strip=True, msg='Wrong output: %s\n\n%s' % (k, s))
def test_simple_distance2(self): edges = [('ant', 'bat'), ('cat', 'bat'), ('bat', 'ant'), ('ant', 'cat')] expected = { 'utf8 True, colour None': ''' destination â•â”€â”€â”€ ant │â•â”€â”€ bat source ││â•â”€ cat ant ·11 bat 1·2 cat 21· ''', 'utf8 True, colour ansi': ''' [4mdestination[0m [0m[37mâ•â”€â”€â”€ ant[0m [37m│[0m[1;30mâ•â”€â”€ bat[0m [4msource[0m [37m│[1;30m│[0m[37mâ•â”€ cat[0m [37m ant[0m [0m[37m·[0m[1;32m1[0m[1;32m1[0m[0m [1;30m bat[0m [1;32m1[0m[0m[1;30m·[0m[33m2[0m[0m [37m cat[0m [33m2[0m[1;32m1[0m[0m[37m·[0m[0m ''', 'utf8 True, colour ansi-heatmap': ''' [4mdestination[0m [0m[37mâ•â”€â”€â”€ ant[0m [37m│[0m[1;30mâ•â”€â”€ bat[0m [4msource[0m [37m│[1;30m│[0m[37mâ•â”€ cat[0m [37m ant[0m [0m[37m·[0m[1;42m1[0m[1;42m1[0m[0m [1;30m bat[0m [1;42m1[0m[0m[1;30m·[0m[43m2[0m[0m [37m cat[0m [43m2[0m[1;42m1[0m[0m[37m·[0m[0m ''', 'utf8 True, colour xterm-256color': ''' [4mdestination[0m [0m[38;5;39mâ•â”€â”€â”€ ant[0m [38;5;39m│[0m[38;5;45mâ•â”€â”€ bat[0m [4msource[0m [38;5;39m│[38;5;45m│[0m[38;5;39mâ•â”€ cat[0m [38;5;39m ant[0m [0m[38;5;39m·[0m[38;5;112m1[0m[38;5;112m1[0m[0m [38;5;45m bat[0m [38;5;112m1[0m[0m[38;5;45m·[0m[38;5;208m2[0m[0m [38;5;39m cat[0m [38;5;208m2[0m[38;5;112m1[0m[0m[38;5;39m·[0m[0m ''', 'utf8 True, colour xterm-256color-heatmap': ''' [4mdestination[0m [0m[38;5;171mâ•â”€â”€â”€ ant[0m [38;5;171m│[0m[38;5;207mâ•â”€â”€ bat[0m [4msource[0m [38;5;171m│[38;5;207m│[0m[38;5;171mâ•â”€ cat[0m [38;5;171m ant[0m [0m[38;5;171m·[0m[48;5;112m1[0m[48;5;112m1[0m[0m [38;5;207m bat[0m [48;5;112m1[0m[0m[38;5;207m·[0m[48;5;208m2[0m[0m [38;5;171m cat[0m [48;5;208m2[0m[48;5;112m1[0m[0m[38;5;171m·[0m[0m ''', 'utf8 False, colour None': ''' destination ,--- ant |,-- bat source ||,- cat ant 011 bat 102 cat 210 ''', 'utf8 False, colour ansi': ''' [4mdestination[0m [0m[37m,--- ant[0m [37m|[0m[1;30m,-- bat[0m [4msource[0m [37m|[1;30m|[0m[37m,- cat[0m [37m ant[0m [0m[37m0[0m[1;32m1[0m[1;32m1[0m[0m [1;30m bat[0m [1;32m1[0m[0m[1;30m0[0m[33m2[0m[0m [37m cat[0m [33m2[0m[1;32m1[0m[0m[37m0[0m[0m ''', 'utf8 False, colour ansi-heatmap': ''' [4mdestination[0m [0m[37m,--- ant[0m [37m|[0m[1;30m,-- bat[0m [4msource[0m [37m|[1;30m|[0m[37m,- cat[0m [37m ant[0m [0m[37m0[0m[1;42m1[0m[1;42m1[0m[0m [1;30m bat[0m [1;42m1[0m[0m[1;30m0[0m[43m2[0m[0m [37m cat[0m [43m2[0m[1;42m1[0m[0m[37m0[0m[0m ''', 'utf8 False, colour xterm-256color': ''' [4mdestination[0m [0m[38;5;39m,--- ant[0m [38;5;39m|[0m[38;5;45m,-- bat[0m [4msource[0m [38;5;39m|[38;5;45m|[0m[38;5;39m,- cat[0m [38;5;39m ant[0m [0m[38;5;39m0[0m[38;5;112m1[0m[38;5;112m1[0m[0m [38;5;45m bat[0m [38;5;112m1[0m[0m[38;5;45m0[0m[38;5;208m2[0m[0m [38;5;39m cat[0m [38;5;208m2[0m[38;5;112m1[0m[0m[38;5;39m0[0m[0m ''', 'utf8 False, colour xterm-256color-heatmap': ''' [4mdestination[0m [0m[38;5;171m,--- ant[0m [38;5;171m|[0m[38;5;207m,-- bat[0m [4msource[0m [38;5;171m|[38;5;207m|[0m[38;5;171m,- cat[0m [38;5;171m ant[0m [0m[38;5;171m0[0m[48;5;112m1[0m[48;5;112m1[0m[0m [38;5;207m bat[0m [48;5;112m1[0m[0m[38;5;207m0[0m[48;5;208m2[0m[0m [38;5;171m cat[0m [48;5;208m2[0m[48;5;112m1[0m[0m[38;5;171m0[0m[0m ''' } for utf8 in (True, False): for colour in self.sorted_colour_sets: k = 'utf8 %s, colour %s' % (utf8, colour) s = graph.distance_matrix(None, edges, utf8=utf8, colour=colour) self.assertStringsEqual(s, expected[k], strip=True, msg='Wrong output: %s\n\n%s' % (k, s))
def test_simple_distance3(self): edges = [('ant', 'bat'), ('bat', 'cat'), ('cat', 'dog'), ('dog', 'ant'), ('dog', 'eel')] expected = { 'utf8 True, colour None': ''' destination â•â”€â”€â”€â”€â”€ ant │â•â”€â”€â”€â”€ bat ││â•â”€â”€â”€ cat │││â•â”€â”€ dog source ││││â•â”€ eel ant ·1234 bat 3·123 cat 23·12 dog 123·1 eel ----· ''', 'utf8 True, colour ansi': ''' [4mdestination[0m [0m[37mâ•â”€â”€â”€â”€â”€ ant[0m [37m│[0m[1;30mâ•â”€â”€â”€â”€ bat[0m [37m│[1;30m│[0m[37mâ•â”€â”€â”€ cat[0m [37m│[1;30m│[37m│[0m[1;30mâ•â”€â”€ dog[0m [4msource[0m [37m│[1;30m│[37m│[1;30m│[0m[37mâ•â”€ eel[0m [37m ant[0m [0m[37m·[0m[1;32m1[0m[33m2[0m[33m3[0m[33m4[0m[0m [1;30m bat[0m [33m3[0m[0m[1;30m·[0m[1;32m1[0m[33m2[0m[33m3[0m[0m [37m cat[0m [33m2[0m[33m3[0m[0m[37m·[0m[1;32m1[0m[33m2[0m[0m [1;30m dog[0m [1;32m1[0m[33m2[0m[33m3[0m[0m[1;30m·[0m[1;32m1[0m[0m [37m eel[0m [1;31m-[1;31m-[1;31m-[1;31m-[0m[37m·[0m[0m ''', 'utf8 True, colour ansi-heatmap': ''' [4mdestination[0m [0m[37mâ•â”€â”€â”€â”€â”€ ant[0m [37m│[0m[1;30mâ•â”€â”€â”€â”€ bat[0m [37m│[1;30m│[0m[37mâ•â”€â”€â”€ cat[0m [37m│[1;30m│[37m│[0m[1;30mâ•â”€â”€ dog[0m [4msource[0m [37m│[1;30m│[37m│[1;30m│[0m[37mâ•â”€ eel[0m [37m ant[0m [0m[37m·[0m[1;42m1[0m[43m2[0m[43m3[0m[43m4[0m[0m [1;30m bat[0m [43m3[0m[0m[1;30m·[0m[1;42m1[0m[43m2[0m[43m3[0m[0m [37m cat[0m [43m2[0m[43m3[0m[0m[37m·[0m[1;42m1[0m[43m2[0m[0m [1;30m dog[0m [1;42m1[0m[43m2[0m[43m3[0m[0m[1;30m·[0m[1;42m1[0m[0m [37m eel[0m [1;41m-[1;41m-[1;41m-[1;41m-[0m[37m·[0m[0m ''', 'utf8 True, colour xterm-256color': ''' [4mdestination[0m [0m[38;5;39mâ•â”€â”€â”€â”€â”€ ant[0m [38;5;39m│[0m[38;5;45mâ•â”€â”€â”€â”€ bat[0m [38;5;39m│[38;5;45m│[0m[38;5;39mâ•â”€â”€â”€ cat[0m [38;5;39m│[38;5;45m│[38;5;39m│[0m[38;5;45mâ•â”€â”€ dog[0m [4msource[0m [38;5;39m│[38;5;45m│[38;5;39m│[38;5;45m│[0m[38;5;39mâ•â”€ eel[0m [38;5;39m ant[0m [0m[38;5;39m·[0m[38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[38;5;208m4[0m[0m [38;5;45m bat[0m [38;5;208m3[0m[0m[38;5;45m·[0m[38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[0m [38;5;39m cat[0m [38;5;214m2[0m[38;5;208m3[0m[0m[38;5;39m·[0m[38;5;112m1[0m[38;5;214m2[0m[0m [38;5;45m dog[0m [38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[0m[38;5;45m·[0m[38;5;112m1[0m[0m [38;5;39m eel[0m [48;5;124m-[48;5;124m-[48;5;124m-[48;5;124m-[0m[38;5;39m·[0m[0m ''', 'utf8 True, colour xterm-256color-heatmap': ''' [4mdestination[0m [0m[38;5;171mâ•â”€â”€â”€â”€â”€ ant[0m [38;5;171m│[0m[38;5;207mâ•â”€â”€â”€â”€ bat[0m [38;5;171m│[38;5;207m│[0m[38;5;171mâ•â”€â”€â”€ cat[0m [38;5;171m│[38;5;207m│[38;5;171m│[0m[38;5;207mâ•â”€â”€ dog[0m [4msource[0m [38;5;171m│[38;5;207m│[38;5;171m│[38;5;207m│[0m[38;5;171mâ•â”€ eel[0m [38;5;171m ant[0m [0m[38;5;171m·[0m[48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[48;5;208m4[0m[0m [38;5;207m bat[0m [48;5;208m3[0m[0m[38;5;207m·[0m[48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[0m [38;5;171m cat[0m [48;5;214m2[0m[48;5;208m3[0m[0m[38;5;171m·[0m[48;5;112m1[0m[48;5;214m2[0m[0m [38;5;207m dog[0m [48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[0m[38;5;207m·[0m[48;5;112m1[0m[0m [38;5;171m eel[0m [48;5;124m-[48;5;124m-[48;5;124m-[48;5;124m-[0m[38;5;171m·[0m[0m ''', 'utf8 False, colour None': ''' destination ,----- ant |,---- bat ||,--- cat |||,-- dog source ||||,- eel ant 01234 bat 30123 cat 23012 dog 12301 eel ----0 ''', 'utf8 False, colour ansi': ''' [4mdestination[0m [0m[37m,----- ant[0m [37m|[0m[1;30m,---- bat[0m [37m|[1;30m|[0m[37m,--- cat[0m [37m|[1;30m|[37m|[0m[1;30m,-- dog[0m [4msource[0m [37m|[1;30m|[37m|[1;30m|[0m[37m,- eel[0m [37m ant[0m [0m[37m0[0m[1;32m1[0m[33m2[0m[33m3[0m[33m4[0m[0m [1;30m bat[0m [33m3[0m[0m[1;30m0[0m[1;32m1[0m[33m2[0m[33m3[0m[0m [37m cat[0m [33m2[0m[33m3[0m[0m[37m0[0m[1;32m1[0m[33m2[0m[0m [1;30m dog[0m [1;32m1[0m[33m2[0m[33m3[0m[0m[1;30m0[0m[1;32m1[0m[0m [37m eel[0m [1;31m-[1;31m-[1;31m-[1;31m-[0m[37m0[0m[0m ''', 'utf8 False, colour ansi-heatmap': ''' [4mdestination[0m [0m[37m,----- ant[0m [37m|[0m[1;30m,---- bat[0m [37m|[1;30m|[0m[37m,--- cat[0m [37m|[1;30m|[37m|[0m[1;30m,-- dog[0m [4msource[0m [37m|[1;30m|[37m|[1;30m|[0m[37m,- eel[0m [37m ant[0m [0m[37m0[0m[1;42m1[0m[43m2[0m[43m3[0m[43m4[0m[0m [1;30m bat[0m [43m3[0m[0m[1;30m0[0m[1;42m1[0m[43m2[0m[43m3[0m[0m [37m cat[0m [43m2[0m[43m3[0m[0m[37m0[0m[1;42m1[0m[43m2[0m[0m [1;30m dog[0m [1;42m1[0m[43m2[0m[43m3[0m[0m[1;30m0[0m[1;42m1[0m[0m [37m eel[0m [1;41m-[1;41m-[1;41m-[1;41m-[0m[37m0[0m[0m ''', 'utf8 False, colour xterm-256color': ''' [4mdestination[0m [0m[38;5;39m,----- ant[0m [38;5;39m|[0m[38;5;45m,---- bat[0m [38;5;39m|[38;5;45m|[0m[38;5;39m,--- cat[0m [38;5;39m|[38;5;45m|[38;5;39m|[0m[38;5;45m,-- dog[0m [4msource[0m [38;5;39m|[38;5;45m|[38;5;39m|[38;5;45m|[0m[38;5;39m,- eel[0m [38;5;39m ant[0m [0m[38;5;39m0[0m[38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[38;5;208m4[0m[0m [38;5;45m bat[0m [38;5;208m3[0m[0m[38;5;45m0[0m[38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[0m [38;5;39m cat[0m [38;5;214m2[0m[38;5;208m3[0m[0m[38;5;39m0[0m[38;5;112m1[0m[38;5;214m2[0m[0m [38;5;45m dog[0m [38;5;112m1[0m[38;5;214m2[0m[38;5;208m3[0m[0m[38;5;45m0[0m[38;5;112m1[0m[0m [38;5;39m eel[0m [48;5;124m-[48;5;124m-[48;5;124m-[48;5;124m-[0m[38;5;39m0[0m[0m ''', 'utf8 False, colour xterm-256color-heatmap': ''' [4mdestination[0m [0m[38;5;171m,----- ant[0m [38;5;171m|[0m[38;5;207m,---- bat[0m [38;5;171m|[38;5;207m|[0m[38;5;171m,--- cat[0m [38;5;171m|[38;5;207m|[38;5;171m|[0m[38;5;207m,-- dog[0m [4msource[0m [38;5;171m|[38;5;207m|[38;5;171m|[38;5;207m|[0m[38;5;171m,- eel[0m [38;5;171m ant[0m [0m[38;5;171m0[0m[48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[48;5;208m4[0m[0m [38;5;207m bat[0m [48;5;208m3[0m[0m[38;5;207m0[0m[48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[0m [38;5;171m cat[0m [48;5;214m2[0m[48;5;208m3[0m[0m[38;5;171m0[0m[48;5;112m1[0m[48;5;214m2[0m[0m [38;5;207m dog[0m [48;5;112m1[0m[48;5;214m2[0m[48;5;208m3[0m[0m[38;5;207m0[0m[48;5;112m1[0m[0m [38;5;171m eel[0m [48;5;124m-[48;5;124m-[48;5;124m-[48;5;124m-[0m[38;5;171m0[0m[0m ''' } for utf8 in (True, False): for colour in self.sorted_colour_sets: k = 'utf8 %s, colour %s' % (utf8, colour) s = graph.distance_matrix(None, edges, utf8=utf8, colour=colour) self.assertStringsEqual(s, expected[k], strip=True, msg='Wrong output: %s\n\n%s' % (k, s))
def test_simple_distance2(self): edges = [('ant', 'bat'), ('cat', 'bat'), ('bat', 'ant'), ('ant', 'cat')] expected = { 'utf8 True, colour None': ''' destination â•â”€â”€â”€ ant │â•â”€â”€ bat source ││â•â”€ cat ant ·11 bat 1·2 cat 21· ''', 'utf8 True, colour ansi': ''' [4mdestination[0m [0m[37mâ•â”€â”€â”€ ant[0m [37m│[0m[1;30mâ•â”€â”€ bat[0m [4msource[0m [37m│[1;30m│[0m[37mâ•â”€ cat[0m [37m ant[0m [0m[37m·[0m[1;32m1[0m[1;32m1[0m[0m [1;30m bat[0m [1;32m1[0m[0m[1;30m·[0m[33m2[0m[0m [37m cat[0m [33m2[0m[1;32m1[0m[0m[37m·[0m[0m ''', 'utf8 True, colour ansi-heatmap': ''' [4mdestination[0m [0m[37mâ•â”€â”€â”€ ant[0m [37m│[0m[1;30mâ•â”€â”€ bat[0m [4msource[0m [37m│[1;30m│[0m[37mâ•â”€ cat[0m [37m ant[0m [0m[37m·[0m[1;42m1[0m[1;42m1[0m[0m [1;30m bat[0m [1;42m1[0m[0m[1;30m·[0m[43m2[0m[0m [37m cat[0m [43m2[0m[1;42m1[0m[0m[37m·[0m[0m ''', 'utf8 True, colour xterm-256color': ''' [4mdestination[0m [0m[38;5;39mâ•â”€â”€â”€ ant[0m [38;5;39m│[0m[38;5;45mâ•â”€â”€ bat[0m [4msource[0m [38;5;39m│[38;5;45m│[0m[38;5;39mâ•â”€ cat[0m [38;5;39m ant[0m [0m[38;5;39m·[0m[38;5;112m1[0m[38;5;112m1[0m[0m [38;5;45m bat[0m [38;5;112m1[0m[0m[38;5;45m·[0m[38;5;208m2[0m[0m [38;5;39m cat[0m [38;5;208m2[0m[38;5;112m1[0m[0m[38;5;39m·[0m[0m ''', 'utf8 True, colour xterm-256color-heatmap': ''' [4mdestination[0m [0m[38;5;171mâ•â”€â”€â”€ ant[0m [38;5;171m│[0m[38;5;207mâ•â”€â”€ bat[0m [4msource[0m [38;5;171m│[38;5;207m│[0m[38;5;171mâ•â”€ cat[0m [38;5;171m ant[0m [0m[38;5;171m·[0m[48;5;112m1[0m[48;5;112m1[0m[0m [38;5;207m bat[0m [48;5;112m1[0m[0m[38;5;207m·[0m[48;5;208m2[0m[0m [38;5;171m cat[0m [48;5;208m2[0m[48;5;112m1[0m[0m[38;5;171m·[0m[0m ''', 'utf8 False, colour None': ''' destination ,--- ant |,-- bat source ||,- cat ant 011 bat 102 cat 210 ''', 'utf8 False, colour ansi': ''' [4mdestination[0m [0m[37m,--- ant[0m [37m|[0m[1;30m,-- bat[0m [4msource[0m [37m|[1;30m|[0m[37m,- cat[0m [37m ant[0m [0m[37m0[0m[1;32m1[0m[1;32m1[0m[0m [1;30m bat[0m [1;32m1[0m[0m[1;30m0[0m[33m2[0m[0m [37m cat[0m [33m2[0m[1;32m1[0m[0m[37m0[0m[0m ''', 'utf8 False, colour ansi-heatmap': ''' [4mdestination[0m [0m[37m,--- ant[0m [37m|[0m[1;30m,-- bat[0m [4msource[0m [37m|[1;30m|[0m[37m,- cat[0m [37m ant[0m [0m[37m0[0m[1;42m1[0m[1;42m1[0m[0m [1;30m bat[0m [1;42m1[0m[0m[1;30m0[0m[43m2[0m[0m [37m cat[0m [43m2[0m[1;42m1[0m[0m[37m0[0m[0m ''', 'utf8 False, colour xterm-256color': ''' [4mdestination[0m [0m[38;5;39m,--- ant[0m [38;5;39m|[0m[38;5;45m,-- bat[0m [4msource[0m [38;5;39m|[38;5;45m|[0m[38;5;39m,- cat[0m [38;5;39m ant[0m [0m[38;5;39m0[0m[38;5;112m1[0m[38;5;112m1[0m[0m [38;5;45m bat[0m [38;5;112m1[0m[0m[38;5;45m0[0m[38;5;208m2[0m[0m [38;5;39m cat[0m [38;5;208m2[0m[38;5;112m1[0m[0m[38;5;39m0[0m[0m ''', 'utf8 False, colour xterm-256color-heatmap': ''' [4mdestination[0m [0m[38;5;171m,--- ant[0m [38;5;171m|[0m[38;5;207m,-- bat[0m [4msource[0m [38;5;171m|[38;5;207m|[0m[38;5;171m,- cat[0m [38;5;171m ant[0m [0m[38;5;171m0[0m[48;5;112m1[0m[48;5;112m1[0m[0m [38;5;207m bat[0m [48;5;112m1[0m[0m[38;5;207m0[0m[48;5;208m2[0m[0m [38;5;171m cat[0m [48;5;208m2[0m[48;5;112m1[0m[0m[38;5;171m0[0m[0m ''' } for utf8 in (True, False): for colour in self.sorted_colour_sets: k = 'utf8 %s, colour %s' % (utf8, colour) s = graph.distance_matrix(None, edges, utf8=utf8, colour=colour) self.assertStringsEqual(s, expected[k], strip=True, msg='Wrong output: %s\n\n%s' % (k, s))