#!/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 os import json import argparse import networkx from libdeps.graph import LibdepsGraph, EdgeProps, 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()