def test_initialization(self):
        """Tests that initialization collapses a class dependency graph."""
        # Create three class nodes (1, 2, 3) in two packages: [1, 2] and [3].

        mock_class_node_1 = create_mock_java_class(pkg=self.TEST_PKG_1)
        mock_class_node_2 = create_mock_java_class(pkg=self.TEST_PKG_1)
        mock_class_node_3 = create_mock_java_class(pkg=self.TEST_PKG_2)

        # Create dependencies (1 -> 3) and (3 -> 2).
        mock_class_graph = unittest.mock.Mock()
        mock_class_graph.nodes = [
            mock_class_node_1, mock_class_node_2, mock_class_node_3
        ]
        mock_class_graph.edges = [(mock_class_node_1, mock_class_node_3),
                                  (mock_class_node_3, mock_class_node_2)]

        test_graph = package_dependency.JavaPackageDependencyGraph(
            mock_class_graph)

        # Expected output: two-node package graph with a bidirectional edge.
        self.assertEqual(test_graph.num_nodes, 2)
        self.assertEqual(test_graph.num_edges, 2)
        self.assertIsNotNone(test_graph.get_node_by_key(self.TEST_PKG_1))
        self.assertIsNotNone(test_graph.get_node_by_key(self.TEST_PKG_2))
        # Ensure there is a bidirectional edge.
        (edge_1_start, edge_1_end), (edge_2_start,
                                     edge_2_end) = test_graph.edges
        self.assertEqual(edge_1_start, edge_2_end)
        self.assertEqual(edge_2_start, edge_1_end)
    def test_create_node_from_key(self):
        """Tests that a JavaPackage is correctly generated."""
        mock_class_graph = unittest.mock.Mock()
        mock_class_graph.nodes = []
        mock_class_graph.edges = []
        test_graph = package_dependency.JavaPackageDependencyGraph(
            mock_class_graph)

        created_node = test_graph.create_node_from_key(self.TEST_PKG_1)
        self.assertEqual(created_node.name, self.TEST_PKG_1)
    def test_initialization_no_dependencies(self):
        """Tests that a package with no external dependencies is included."""
        # Create one class node (1) in one package: [1].
        mock_class_node = create_mock_java_class(pkg=self.TEST_PKG_1)

        # Do not create any dependencies.
        mock_class_graph = unittest.mock.Mock()
        mock_class_graph.nodes = [mock_class_node]
        mock_class_graph.edges = []

        test_graph = package_dependency.JavaPackageDependencyGraph(
            mock_class_graph)

        # Expected output: one-node package graph with no edges.
        self.assertEqual(test_graph.num_nodes, 1)
        self.assertEqual(test_graph.num_edges, 0)
        self.assertIsNotNone(test_graph.get_node_by_key(self.TEST_PKG_1))
Example #4
0
def load_class_and_package_graphs_from_file(
    filename: str
) -> Tuple[class_dependency.JavaClassDependencyGraph,
           package_dependency.JavaPackageDependencyGraph, Dict]:
    """Recreates a Java(Class+Package)DependencyGraph from a JSON file.

    The file is expected to be in the format dumped by
    `dump_class_and_package_graphs_to_file`.

    Note that we construct the package graph from the deserialized class graph,
    not using the serialized package graph at all. This aligns with how we
    construct the package graph when using jdeps. However, we still output
    a serialized package graph for other consumers of the JSON (eg. JS-side)
    which may want to bypass the costly conversion from class to package graph.
    """
    class_graph, metadata = load_class_graph_from_file(filename)
    package_graph = package_dependency.JavaPackageDependencyGraph(class_graph)
    return class_graph, package_graph, metadata
Example #5
0
    def test_package_serialization(self):
        """Tests JSON serialization of a package dependency graph."""
        class_graph = class_dependency.JavaClassDependencyGraph()
        class_graph.add_edge_if_new(self.CLASS_1, self.CLASS_2)
        class_graph.add_edge_if_new(self.CLASS_1, self.CLASS_3)
        class_graph.add_edge_if_new(self.CLASS_2, self.CLASS_3)
        class_graph.get_node_by_key(self.CLASS_1).add_nested_class(
            self.CLASS_1_NESTED_1)
        class_graph.get_node_by_key(self.CLASS_1).add_nested_class(
            self.CLASS_1_NESTED_2)
        class_graph.get_node_by_key(self.CLASS_2).add_nested_class(
            self.CLASS_2_NESTED_1)

        package_graph = package_dependency.JavaPackageDependencyGraph(
            class_graph)
        test_json_obj = serialization.create_json_obj_from_graph(package_graph)

        self.assertEqual(test_json_obj, self.JSON_PACKAGE_GRAPH)
Example #6
0
def main():
    """Runs jdeps and creates a JSON file from the output."""
    arg_parser = argparse.ArgumentParser(
        description='Runs jdeps (dependency analysis tool) on a given JAR and '
        'writes the resulting dependency graph into a JSON file.')
    required_arg_group = arg_parser.add_argument_group('required arguments')
    required_arg_group.add_argument(
        '-t',
        '--target',
        required=True,
        help='Path to the JAR file to run jdeps on.')
    required_arg_group.add_argument(
        '-o',
        '--output',
        required=True,
        help='Path to the file to write JSON output to. Will be created '
        'if it does not yet exist and overwrite existing '
        'content if it does.')
    arg_parser.add_argument('-j',
                            '--jdeps-path',
                            default=JDEPS_PATH,
                            help='Path to the jdeps executable.')
    arguments = arg_parser.parse_args()

    print('Running jdeps and parsing output...')
    raw_jdeps_output = run_jdeps(arguments.jdeps_path, arguments.target)
    jdeps_parser = JavaClassJdepsParser()
    jdeps_parser.parse_raw_jdeps_output(raw_jdeps_output)

    class_graph = jdeps_parser.graph
    print(f'Parsed class-level dependency graph, '
          f'got {class_graph.num_nodes} nodes '
          f'and {class_graph.num_edges} edges.')

    package_graph = package_dependency.JavaPackageDependencyGraph(class_graph)
    print(f'Created package-level dependency graph, '
          f'got {package_graph.num_nodes} nodes '
          f'and {package_graph.num_edges} edges.')

    print(f'Dumping JSON representation to {arguments.output}.')
    serialization.dump_class_and_package_graphs_to_file(
        class_graph, package_graph, arguments.output)
    def test_initialization_internal_dependencies(self):
        """Tests that a package with only internal dependencies is included."""
        # Create two class nodes (1, 2) in one package: [1, 2].
        mock_class_node_1 = create_mock_java_class(pkg=self.TEST_PKG_1)
        mock_class_node_2 = create_mock_java_class(pkg=self.TEST_PKG_1)

        # Create a dependency (1 -> 2).
        mock_class_graph = unittest.mock.Mock()
        mock_class_graph.nodes = [mock_class_node_1, mock_class_node_2]
        mock_class_graph.edges = [(mock_class_node_1, mock_class_node_2)]

        test_graph = package_dependency.JavaPackageDependencyGraph(
            mock_class_graph)

        # Expected output: one-node package graph with a self-edge.
        self.assertEqual(test_graph.num_nodes, 1)
        self.assertEqual(test_graph.num_edges, 1)
        self.assertIsNotNone(test_graph.get_node_by_key(self.TEST_PKG_1))
        # Ensure there is a self-edge.
        [(edge_start, edge_end)] = test_graph.edges
        self.assertEqual(edge_start, edge_end)
def main():
    """Runs jdeps on all JARs a build target depends on.

    Creates a JSON file from the jdeps output."""
    arg_parser = argparse.ArgumentParser(
        description='Runs jdeps (dependency analysis tool) on all JARs a root '
        'build target depends on and writes the resulting dependency graph '
        'into a JSON file. The default root build target is '
        'chrome/android:monochrome_public_bundle.')
    required_arg_group = arg_parser.add_argument_group('required arguments')
    required_arg_group.add_argument('-C',
                                    '--build_output_dir',
                                    required=True,
                                    help='Build output directory.')
    required_arg_group.add_argument(
        '-o',
        '--output',
        required=True,
        help='Path to the file to write JSON output to. Will be created '
        'if it does not yet exist and overwrite existing '
        'content if it does.')
    arg_parser.add_argument('-t',
                            '--target',
                            default=DEFAULT_ROOT_TARGET,
                            help='Root build target.')
    arg_parser.add_argument('-j',
                            '--jdeps-path',
                            default=JDEPS_PATH,
                            help='Path to the jdeps executable.')
    arguments = arg_parser.parse_args()

    print('Getting list of dependency jars...')
    gn_desc_output = _run_gn_desc_list_dependencies(arguments.build_output_dir,
                                                    arguments.target)
    target_jars: JarTargetList = list_original_targets_and_jars(
        gn_desc_output, arguments.build_output_dir)

    print('Running jdeps...')
    # jdeps already has some parallelism
    jdeps_process_number = math.ceil(multiprocessing.cpu_count() / 2)
    with multiprocessing.Pool(jdeps_process_number) as pool:
        jar_paths = [target_jar for _, target_jar in target_jars]
        jdeps_outputs = pool.map(
            functools.partial(_run_jdeps, arguments.jdeps_path), jar_paths)

    print('Parsing jdeps output...')
    jdeps_parser = JavaClassJdepsParser()
    for raw_jdeps_output, (build_target, _) in zip(jdeps_outputs, target_jars):
        jdeps_parser.parse_raw_jdeps_output(build_target, raw_jdeps_output)

    class_graph = jdeps_parser.graph
    print(f'Parsed class-level dependency graph, '
          f'got {class_graph.num_nodes} nodes '
          f'and {class_graph.num_edges} edges.')

    package_graph = package_dependency.JavaPackageDependencyGraph(class_graph)
    print(f'Created package-level dependency graph, '
          f'got {package_graph.num_nodes} nodes '
          f'and {package_graph.num_edges} edges.')

    print(f'Dumping JSON representation to {arguments.output}.')
    serialization.dump_class_and_package_graphs_to_file(
        class_graph, package_graph, arguments.output)
def main():
    """Runs jdeps on all JARs a build target depends on.

    Creates a JSON file from the jdeps output."""
    arg_parser = argparse.ArgumentParser(
        description='Runs jdeps (dependency analysis tool) on all JARs a root '
        'build target depends on and writes the resulting dependency graph '
        'into a JSON file. The default root build target is '
        'chrome/android:monochrome_public_bundle.')
    required_arg_group = arg_parser.add_argument_group('required arguments')
    required_arg_group.add_argument('-C',
                                    '--build_output_dir',
                                    required=True,
                                    help='Build output directory.')
    required_arg_group.add_argument(
        '-o',
        '--output',
        required=True,
        help='Path to the file to write JSON output to. Will be created '
        'if it does not yet exist and overwrite existing '
        'content if it does.')
    arg_parser.add_argument('-t',
                            '--target',
                            default=DEFAULT_ROOT_TARGET,
                            help='Root build target.')
    arg_parser.add_argument('-d',
                            '--checkout-dir',
                            help='Path to the chromium checkout directory.')
    arg_parser.add_argument('-j',
                            '--jdeps-path',
                            help='Path to the jdeps executable.')
    arg_parser.add_argument('-g',
                            '--gn-path',
                            default='gn',
                            help='Path to the gn executable.')
    arguments = arg_parser.parse_args()

    if arguments.checkout_dir:
        src_path = pathlib.Path(arguments.checkout_dir)
    else:
        src_path = pathlib.Path(__file__).resolve().parents[3]

    if arguments.jdeps_path:
        jdeps_path = pathlib.Path(arguments.jdeps_path)
    else:
        jdeps_path = src_path.joinpath('third_party/jdk/current/bin/jdeps')

    # gn and git must be run from inside the git checkout.
    os.chdir(src_path)

    cr_position_str = git_utils.get_last_commit_cr_position()
    cr_position = int(cr_position_str) if cr_position_str else 0

    print('Getting list of dependency jars...')
    gn_desc_output = _run_gn_desc_list_dependencies(arguments.build_output_dir,
                                                    arguments.target,
                                                    arguments.gn_path)
    target_jars: JarTargetList = list_original_targets_and_jars(
        gn_desc_output, arguments.build_output_dir, cr_position)

    print('Running jdeps...')
    # jdeps already has some parallelism
    jdeps_process_number = math.ceil(multiprocessing.cpu_count() / 2)
    with multiprocessing.Pool(jdeps_process_number) as pool:
        jar_paths = [target_jar for _, target_jar in target_jars]
        jdeps_outputs = pool.map(functools.partial(_run_jdeps, jdeps_path),
                                 jar_paths)

    print('Parsing jdeps output...')
    jdeps_parser = JavaClassJdepsParser()
    for raw_jdeps_output, (build_target, _) in zip(jdeps_outputs, target_jars):
        jdeps_parser.parse_raw_jdeps_output(build_target, raw_jdeps_output)

    class_graph = jdeps_parser.graph
    print(f'Parsed class-level dependency graph, '
          f'got {class_graph.num_nodes} nodes '
          f'and {class_graph.num_edges} edges.')

    package_graph = package_dependency.JavaPackageDependencyGraph(class_graph)
    print(f'Created package-level dependency graph, '
          f'got {package_graph.num_nodes} nodes '
          f'and {package_graph.num_edges} edges.')

    print(f'Dumping JSON representation to {arguments.output}.')
    serialization.dump_class_and_package_graphs_to_file(
        class_graph, package_graph, arguments.output)