def dotest(outputname, nostamp=True): pdf = FPDF(unit="pt") pdf._putinfo = lambda: common.test_putinfo(pdf) pdf.add_page() pdf.set_font("Times", size=12) pdf.cell(0, 12, "Dummy content") # Get the PDF data the usual way via a real file pdf.output(outputname) with open(outputname, "rb") as file: data = file.read(1000) assert len(data) == 966, "Unexpected PDF file size" try: # Python < 3 (Python 2.5 does not have the "io" module) from cStringIO import StringIO capture = StringIO() detach = lambda: capture except ImportError: # Python >= 3.1 from io import TextIOWrapper, BytesIO # Ensure that no text encoding is actually done capture = TextIOWrapper(BytesIO(), "undefined") detach = lambda: capture.detach() # Compare data when output() writes to stdout original_stdout = sys.stdout try: sys.stdout = capture pdf.output() capture = detach() finally: sys.stdout = original_stdout assert capture.getvalue() == data, "Unexpected stdout data" # Compare data when output() returns a byte string returned = pdf.output(dest="S") assert isinstance(returned, bytes), "output() should return bytes" assert returned == data, "Unexpected PDF data returned"
def show_graph(objs, edge_func, swap_source_target, max_depth=3, extra_ignore=(), filter=None, too_many=10, highlight=None, filename=None, extra_info=None, refcounts=False, shortnames=True): if not isinstance(objs, (list, tuple)): objs = [objs] # Write to a file-like string buffer, rather than disk. if filename == "string": f = StringIO() if filename and filename.endswith('.dot'): f = codecs.open(filename, 'w', encoding='utf-8') dot_filename = filename else: fd, dot_filename = tempfile.mkstemp(prefix='objgraph-', suffix='.dot', text=True) f = os.fdopen(fd, "w") if getattr(f, 'encoding', None): # Python 3 will wrap the file in the user's preferred encoding # Re-wrap it for utf-8 import io f = io.TextIOWrapper(f.detach(), 'utf-8') f.write('digraph ObjectGraph {\n' ' node[shape=box, style=filled, fillcolor=white];\n') queue = [] depth = {} ignore = set(extra_ignore) ignore.add(id(objs)) ignore.add(id(extra_ignore)) ignore.add(id(queue)) ignore.add(id(depth)) ignore.add(id(ignore)) ignore.add(id(sys._getframe())) # this function ignore.add(id(sys._getframe().f_locals)) ignore.add(id(sys._getframe(1))) # show_refs/show_backrefs, most likely ignore.add(id(sys._getframe(1).f_locals)) for obj in objs: f.write(' %s[fontcolor=red];\n' % (obj_node_id(obj))) depth[id(obj)] = 0 queue.append(obj) del obj gc.collect() nodes = 0 while queue: nodes += 1 target = queue.pop(0) tdepth = depth[id(target)] f.write(' %s[label="%s"];\n' % (obj_node_id(target), obj_label(target, extra_info, refcounts, shortnames))) h, s, v = gradient((0, 0, 1), (0, 0, .3), tdepth, max_depth) if inspect.ismodule(target): h = .3 s = 1 if highlight and highlight(target): h = .6 s = .6 v = 0.5 + v * 0.5 f.write(' %s[fillcolor="%g,%g,%g"];\n' % (obj_node_id(target), h, s, v)) if v < 0.5: f.write(' %s[fontcolor=white];\n' % (obj_node_id(target))) if hasattr(getattr(target, '__class__', None), '__del__'): f.write(" %s->%s_has_a_del[color=red,style=dotted,len=0.25,weight=10];\n" % (obj_node_id(target), obj_node_id(target))) f.write(' %s_has_a_del[label="__del__",shape=doublecircle,height=0.25,color=red,fillcolor="0,.5,1",fontsize=6];\n' % (obj_node_id(target))) if tdepth >= max_depth: continue if is_proper_module(target) and not swap_source_target: # For show_backrefs(), it makes sense to stop when reaching a # module because you'll end up in sys.modules and explode the # graph with useless clutter. For show_refs(), it makes sense # to continue. continue neighbours = edge_func(target) ignore.add(id(neighbours)) n = 0 skipped = 0 for source in neighbours: if id(source) in ignore: continue if filter and not filter(source): continue if n >= too_many: skipped += 1 continue if swap_source_target: srcnode, tgtnode = target, source else: srcnode, tgtnode = source, target elabel = edge_label(srcnode, tgtnode, shortnames) f.write(' %s -> %s%s;\n' % (obj_node_id(srcnode), obj_node_id(tgtnode), elabel)) if id(source) not in depth: depth[id(source)] = tdepth + 1 queue.append(source) n += 1 del source del neighbours if skipped > 0: h, s, v = gradient((0, 1, 1), (0, 1, .3), tdepth + 1, max_depth) if swap_source_target: label = "%d more references" % skipped edge = "%s->too_many_%s" % (obj_node_id(target), obj_node_id(target)) else: label = "%d more backreferences" % skipped edge = "too_many_%s->%s" % (obj_node_id(target), obj_node_id(target)) f.write(' %s[color=red,style=dotted,len=0.25,weight=10];\n' % edge) f.write(' too_many_%s[label="%s",shape=box,height=0.25,color=red,fillcolor="%g,%g,%g",fontsize=6];\n' % (obj_node_id(target), label, h, s, v)) f.write(' too_many_%s[fontcolor=white];\n' % (obj_node_id(target))) f.write("}\n") # If writing to a string, perform cleanup and return early. if filename == "string": graph = f.getvalue() f.close() return graph else: print("Graph written to %s (%d nodes)" % (dot_filename, nodes)) f.close() if filename and filename.endswith('.dot'): # nothing else to do, the user asked for a .dot file return if not filename and program_in_path('xdot'): print("Spawning graph viewer (xdot)") subprocess.Popen(['xdot', dot_filename], close_fds=True) elif program_in_path('dot'): if not filename: print("Graph viewer (xdot) not found, generating a png instead") filename = dot_filename[:-4] + '.png' stem, ext = os.path.splitext(filename) f = open(filename, 'wb') dot = subprocess.Popen(['dot', ('-T' + ext[1:]), dot_filename], stdout=f, close_fds=False) dot.wait() if dot.returncode != 0: # XXX: shouldn't this go to stderr or a log? print("dot failed to generate '%s' image: output format not supported?") f.close() print("Image generated as %s" % filename) else: if filename: print("Graph viewer (xdot) and image renderer (dot) not found, not doing anything else") else: print("Unrecognized file type (%s), not doing anything else" % filename)