#!/usr/bin/env python3 # # Copyright 2022 MongoDB Inc. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # """Test graphs for the graph visualizer and analyzer.""" import argparse import json import os import networkx from libdeps.graph import EdgeProps, LibdepsGraph, NodeProps def get_args(): """Create the argparse and return passed args.""" parser = argparse.ArgumentParser() parser.add_argument( "--graph-output-dir", type=str, action="store", help="Directory test graphml files will be saved.", default="build/opt/libdeps", ) parser.add_argument( "--generate-big-graphs", action="store_true", help="Makes graphs which are large for testing scale.", default=False, ) return parser.parse_args() def add_node(graph, node, builder): """Add a node to the graph.""" graph.add_nodes_from([(node, {NodeProps.bin_type.name: builder})]) def add_edge(graph, from_node, to_node, **kwargs): """Add an edge to the graph.""" edge_props = { EdgeProps.direct.name: kwargs[EdgeProps.direct.name], EdgeProps.visibility.name: int(kwargs[EdgeProps.visibility.name]), } if kwargs.get("symbols"): edge_props[EdgeProps.symbols.name] = kwargs.get("symbols") graph.add_edges_from([(from_node, to_node, edge_props)]) def get_big_graph(int_id): """Generate a big graph.""" graph = LibdepsGraph() graph.graph["build_dir"] = "." graph.graph["graph_schema_version"] = 2 graph.graph["deptypes"] = json.dumps( { "Global": 0, "Public": 1, "Private": 2, "Interface": 3, } ) graph.graph["git_hash"] = f"BIG{int_id.zfill(4)}" num_nodes = 200 for i in range(num_nodes): add_node(graph, f"lib{i}.so", "SharedLibrary") for j in range(num_nodes - i): add_edge( graph, f"lib{i}.so", f"lib{j}.so", direct=True, visibility=graph.get_deptype("Public"), symbols="\n".join([f"RandomString{i+j}" * 100 for i in range(10)]), ) return graph def get_double_diamond_mock_graph(): """Construct a mock graph which covers a double diamond structure.""" graph = LibdepsGraph() graph.graph["build_dir"] = "." graph.graph["graph_schema_version"] = 2 graph.graph["deptypes"] = json.dumps( { "Global": 0, "Public": 1, "Private": 2, "Interface": 3, } ) graph.graph["git_hash"] = "TEST001" # builds a graph of mostly public edges that looks like this: # # # /lib3.so /lib7.so # | \ | \ # <-lib1.so--lib2.so lib5.so--lib6.so lib9.so # | / | / # \lib4.so \lib8.so # add_node(graph, "lib1.so", "SharedLibrary") add_node(graph, "lib2.so", "SharedLibrary") add_node(graph, "lib3.so", "SharedLibrary") add_node(graph, "lib4.so", "SharedLibrary") add_node(graph, "lib5.so", "SharedLibrary") add_node(graph, "lib6.so", "SharedLibrary") add_node(graph, "lib7.so", "SharedLibrary") add_node(graph, "lib8.so", "SharedLibrary") add_node(graph, "lib9.so", "SharedLibrary") add_edge(graph, "lib1.so", "lib2.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib2.so", "lib3.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib2.so", "lib4.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib3.so", "lib5.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib4.so", "lib5.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib5.so", "lib6.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib6.so", "lib7.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib6.so", "lib8.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib7.so", "lib9.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib8.so", "lib9.so", direct=True, visibility=graph.get_deptype("Public")) # trans for 3 and 4 add_edge(graph, "lib1.so", "lib3.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib1.so", "lib4.so", direct=False, visibility=graph.get_deptype("Public")) # trans for 5 add_edge(graph, "lib1.so", "lib5.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib2.so", "lib5.so", direct=False, visibility=graph.get_deptype("Public")) # trans for 6 add_edge(graph, "lib1.so", "lib6.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib2.so", "lib6.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib3.so", "lib6.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib4.so", "lib6.so", direct=False, visibility=graph.get_deptype("Public")) # trans for 7 add_edge(graph, "lib1.so", "lib7.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib2.so", "lib7.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib3.so", "lib7.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib4.so", "lib7.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib5.so", "lib7.so", direct=False, visibility=graph.get_deptype("Public")) # trans for 8 add_edge(graph, "lib1.so", "lib8.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib2.so", "lib8.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib3.so", "lib8.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib4.so", "lib8.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib5.so", "lib8.so", direct=False, visibility=graph.get_deptype("Public")) # trans for 9 add_edge(graph, "lib1.so", "lib9.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib2.so", "lib9.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib3.so", "lib9.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib4.so", "lib9.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib5.so", "lib9.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib6.so", "lib9.so", direct=False, visibility=graph.get_deptype("Public")) return graph def get_basic_mock_graph(): """Construct a mock graph which covers most cases and is easy to understand.""" graph = LibdepsGraph() graph.graph["build_dir"] = "." graph.graph["graph_schema_version"] = 2 graph.graph["deptypes"] = json.dumps( { "Global": 0, "Public": 1, "Private": 2, "Interface": 3, } ) graph.graph["git_hash"] = "TEST002" # builds a graph of mostly public edges: # # /-lib5.so # /lib3.so # | \-lib6.so # <-lib1.so--lib2.so # | /-lib5.so (private) # \lib4.so # \-lib6.so # nodes add_node(graph, "lib1.so", "SharedLibrary") add_node(graph, "lib2.so", "SharedLibrary") add_node(graph, "lib3.so", "SharedLibrary") add_node(graph, "lib4.so", "SharedLibrary") add_node(graph, "lib5.so", "SharedLibrary") add_node(graph, "lib6.so", "SharedLibrary") # direct edges add_edge(graph, "lib1.so", "lib2.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib2.so", "lib3.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib2.so", "lib4.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib4.so", "lib6.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib3.so", "lib5.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib3.so", "lib6.so", direct=True, visibility=graph.get_deptype("Public")) add_edge(graph, "lib4.so", "lib5.so", direct=True, visibility=graph.get_deptype("Private")) # trans for 3 add_edge(graph, "lib1.so", "lib3.so", direct=False, visibility=graph.get_deptype("Public")) # trans for 4 add_edge(graph, "lib1.so", "lib4.so", direct=False, visibility=graph.get_deptype("Public")) # trans for 5 add_edge(graph, "lib2.so", "lib5.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib1.so", "lib5.so", direct=False, visibility=graph.get_deptype("Public")) # trans for 6 add_edge(graph, "lib2.so", "lib6.so", direct=False, visibility=graph.get_deptype("Public")) add_edge(graph, "lib1.so", "lib6.so", direct=False, visibility=graph.get_deptype("Public")) return graph def get_basic_mock_directory_graph(): """Construct a mock graph which covers most cases and is easy to understand.""" graph = LibdepsGraph() graph.graph["build_dir"] = "." graph.graph["graph_schema_version"] = 2 graph.graph["deptypes"] = json.dumps( { "Global": 0, "Public": 1, "Private": 2, "Interface": 3, } ) graph.graph["git_hash"] = "TEST003" # builds a graph of mostly public edges: # # /-lib5.so # /lib3 # | \-lib6.so # <-lib1.so--lib2 # | /-lib5.so (private) # \lib4.so # \-lib6.so # nodes add_node(graph, "dir1/lib1.so", "SharedLibrary") add_node(graph, "dir1/sub1/lib2", "Program") add_node(graph, "dir1/sub1/lib3", "Program") add_node(graph, "dir1/sub2/lib4.so", "SharedLibrary") add_node(graph, "dir2/lib5.so", "SharedLibrary") add_node(graph, "dir2/lib6.so", "SharedLibrary") # direct edges add_edge( graph, "dir1/lib1.so", "dir1/sub1/lib2", direct=True, visibility=graph.get_deptype("Public") ) add_edge( graph, "dir1/sub1/lib2", "dir1/sub1/lib3", direct=True, visibility=graph.get_deptype("Public"), ) add_edge( graph, "dir1/sub1/lib2", "dir1/sub2/lib4.so", direct=True, visibility=graph.get_deptype("Public"), ) add_edge( graph, "dir1/sub2/lib4.so", "dir2/lib6.so", direct=True, visibility=graph.get_deptype("Public"), ) add_edge( graph, "dir1/sub1/lib3", "dir2/lib5.so", direct=True, visibility=graph.get_deptype("Public") ) add_edge( graph, "dir1/sub1/lib3", "dir2/lib6.so", direct=True, visibility=graph.get_deptype("Public") ) add_edge( graph, "dir1/sub2/lib4.so", "dir2/lib5.so", direct=True, visibility=graph.get_deptype("Private"), ) # trans for 3 add_edge( graph, "dir1/lib1.so", "dir1/sub1/lib3", direct=False, visibility=graph.get_deptype("Public"), ) # trans for 4 add_edge( graph, "dir1/lib1.so", "dir1/sub2/lib4.so", direct=False, visibility=graph.get_deptype("Public"), ) # trans for 5 add_edge( graph, "dir1/sub1/lib2", "dir2/lib5.so", direct=False, visibility=graph.get_deptype("Public"), ) add_edge( graph, "dir1/lib1.so", "dir2/lib5.so", direct=False, visibility=graph.get_deptype("Public") ) # trans for 6 add_edge( graph, "dir1/sub1/lib2", "dir2/lib6.so", direct=False, visibility=graph.get_deptype("Public"), ) add_edge( graph, "dir1/lib1.so", "dir2/lib6.so", direct=False, visibility=graph.get_deptype("Public") ) return graph def get_simple_directory_graph(): """Construct a mock graph which covers most cases and is easy to understand.""" graph = LibdepsGraph() graph.graph["build_dir"] = "." graph.graph["graph_schema_version"] = 2 graph.graph["deptypes"] = json.dumps( { "Global": 0, "Public": 1, "Private": 2, "Interface": 3, } ) graph.graph["git_hash"] = "TEST004" # lib2.so <- lib4.so # /∧ \∨ # lib1.so prog1 <- lib5.so # \∨ /∧ # lib3.so -> prog2 # nodes add_node(graph, "mongo/base/lib1.so", "SharedLibrary") add_node(graph, "mongo/base/lib2.so", "SharedLibrary") add_node(graph, "mongo/db/lib3.so", "SharedLibrary") add_node(graph, "third_party/lib4.so", "SharedLibrary") add_node(graph, "third_party/lib5.so", "SharedLibrary") add_node(graph, "mongo/base/prog1", "Program") add_node(graph, "mongo/db/prog2", "Program") # direct edges add_edge( graph, "mongo/base/lib1.so", "mongo/base/lib2.so", direct=True, visibility=graph.get_deptype("Public"), ) add_edge( graph, "mongo/base/lib1.so", "mongo/db/lib3.so", direct=True, visibility=graph.get_deptype("Public"), ) add_edge( graph, "mongo/base/lib2.so", "mongo/base/prog1", direct=True, visibility=graph.get_deptype("Public"), ) add_edge( graph, "mongo/db/lib3.so", "mongo/base/prog1", direct=True, visibility=graph.get_deptype("Public"), ) add_edge( graph, "mongo/db/lib3.so", "mongo/db/prog2", direct=True, visibility=graph.get_deptype("Public"), ) add_edge( graph, "third_party/lib4.so", "mongo/base/lib2.so", direct=True, visibility=graph.get_deptype("Public"), ) add_edge( graph, "third_party/lib5.so", "mongo/base/prog1", direct=True, visibility=graph.get_deptype("Public"), ) return graph def save_graph_file(graph, output_dir): """Save a graph locally as a .graphml.""" filename = output_dir + "/libdeps_" + graph.graph["git_hash"] + ".graphml" networkx.write_graphml(graph, filename, named_key_ids=True) def main(): """Generate and save the test graphs as .graphml files.""" args = get_args() output_dir = args.graph_output_dir os.makedirs(output_dir, exist_ok=True) graph = get_double_diamond_mock_graph() save_graph_file(graph, output_dir) graph = get_basic_mock_graph() save_graph_file(graph, output_dir) graph = get_basic_mock_directory_graph() save_graph_file(graph, output_dir) graph = get_simple_directory_graph() save_graph_file(graph, output_dir) if args.generate_big_graphs: graph = get_big_graph("0") for i in range(1, 30): print(f"generating big graph {i}...") graph.graph["git_hash"] = f"BIG{str(i).zfill(4)}" save_graph_file(graph, output_dir) if __name__ == "__main__": main()