Commit d16518a3 authored by James Long's avatar James Long Committed by Commit Bot

Add unit tests for Java class dependency analysis

Unit tests are added for the three existing files in the
/src/tools/android/dependency_analysis directory, and a presubmit file
is added to run the tests automatically.

A small bug was also fixed in `process_jdeps.py` to only ignore lines of
the form `a -> not found   b` instead of ignoring lines with the substring `not
found` entirely (a valid form is `a -> b   not found`)

Bug: 1085063
Change-Id: I5e4d747977c1eba02b4416a083010f003dab15a4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2208301Reviewed-by: default avatarHenrique Nakashima <hnakashima@chromium.org>
Reviewed-by: default avatarMohamed Heikal <mheikal@chromium.org>
Commit-Queue: James Long <yjlong@google.com>
Cr-Commit-Position: refs/heads/master@{#771171}
parent 6321888e
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Top-level presubmit script for dependency_analysis.
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details on the presubmit API built into depot_tools.
"""
def CommonChecks(input_api, output_api):
checks = input_api.canned_checks.GetUnitTestsRecursively(
input_api,
output_api,
input_api.PresubmitLocalPath(),
whitelist=[r'.+_unittest\.py$'],
blacklist=[],
run_on_python2=False,
run_on_python3=True)
return input_api.RunTests(checks, False)
def CheckChangeOnUpload(input_api, output_api):
return CommonChecks(input_api, output_api)
def CheckChangeOnCommit(input_api, output_api):
return CommonChecks(input_api, output_api)
...@@ -10,20 +10,20 @@ import graph ...@@ -10,20 +10,20 @@ import graph
# Matches w/o parens: (some.package.name).(class)$($optional$nested$class) # Matches w/o parens: (some.package.name).(class)$($optional$nested$class)
JAVA_CLASS_FULL_NAME_REGEX = re.compile( JAVA_CLASS_FULL_NAME_REGEX = re.compile(
r"^(?P<package>.*)\.(?P<class_name>.*?)(\$(?P<nested>.*))?$") r'^(?P<package>.*)\.(?P<class_name>.*?)(\$(?P<nested>.*))?$')
def java_class_params_to_key(package: str, class_name: str): def java_class_params_to_key(package: str, class_name: str):
"""Returns the unique key created from a package and class name.""" """Returns the unique key created from a package and class name."""
return f"{package}.{class_name}" return f'{package}.{class_name}'
def split_nested_class_from_key(key: str) -> Tuple[str, str]: def split_nested_class_from_key(key: str) -> Tuple[str, str]:
"""Splits a jdeps class name into its key and nested class, if any.""" """Splits a jdeps class name into its key and nested class, if any."""
re_match = JAVA_CLASS_FULL_NAME_REGEX.match(key) re_match = JAVA_CLASS_FULL_NAME_REGEX.match(key)
package = re_match.group("package") package = re_match.group('package')
class_name = re_match.group("class_name") class_name = re_match.group('class_name')
nested = re_match.group("nested") nested = re_match.group('nested')
return java_class_params_to_key(package, class_name), nested return java_class_params_to_key(package, class_name), nested
...@@ -33,6 +33,9 @@ class JavaClass(graph.Node): ...@@ -33,6 +33,9 @@ class JavaClass(graph.Node):
Some classes may have nested classes (eg. explicitly, or Some classes may have nested classes (eg. explicitly, or
implicitly through lambdas). We treat these nested classes as part of implicitly through lambdas). We treat these nested classes as part of
the outer class, storing only their names as metadata. the outer class, storing only their names as metadata.
Attributes:
nested_classes: A set of nested classes contained within this class.
""" """
def __init__(self, package: str, class_name: str): def __init__(self, package: str, class_name: str):
"""Initializes a new Java class structure. """Initializes a new Java class structure.
...@@ -51,6 +54,11 @@ class JavaClass(graph.Node): ...@@ -51,6 +54,11 @@ class JavaClass(graph.Node):
self._nested_classes = set() self._nested_classes = set()
@property
def nested_classes(self):
"""A set of nested classes contained within this class."""
return self._nested_classes
def add_nested_class(self, nested: str): # pylint: disable=missing-function-docstring def add_nested_class(self, nested: str): # pylint: disable=missing-function-docstring
self._nested_classes.add(nested) self._nested_classes.add(nested)
...@@ -63,8 +71,8 @@ class JavaClassDependencyGraph(graph.Graph): ...@@ -63,8 +71,8 @@ class JavaClassDependencyGraph(graph.Graph):
def create_node_from_key(self, key: str): def create_node_from_key(self, key: str):
"""See comment above the regex definition.""" """See comment above the regex definition."""
re_match = JAVA_CLASS_FULL_NAME_REGEX.match(key) re_match = JAVA_CLASS_FULL_NAME_REGEX.match(key)
package = re_match.group("package") package = re_match.group('package')
class_name = re_match.group("class_name") class_name = re_match.group('class_name')
return JavaClass(package, class_name) return JavaClass(package, class_name)
def add_nested_class_to_key(self, key: str, nested: str): # pylint: disable=missing-function-docstring def add_nested_class_to_key(self, key: str, nested: str): # pylint: disable=missing-function-docstring
......
#!/usr/bin/env python3
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for dependency_analysis.class_dependency."""
import unittest
import class_dependency
class TestHelperFunctions(unittest.TestCase):
"""Unit tests for module-level helper functions."""
def test_java_class_params_to_key(self):
"""Tests that the helper concatenates, separated with a dot."""
result = class_dependency.java_class_params_to_key('pkg.name', 'class')
self.assertEqual(result, 'pkg.name.class')
def test_split_nested_class_from_key(self):
"""Tests that the helper correctly splits out a nested class"""
part1, part2 = class_dependency.split_nested_class_from_key(
'pkg.name.class$nested')
self.assertEqual(part1, 'pkg.name.class')
self.assertEqual(part2, 'nested')
def test_split_nested_class_from_key_no_nested(self):
"""Tests that the helper works when there is no nested class"""
part1, part2 = class_dependency.split_nested_class_from_key(
'pkg.name.class')
self.assertEqual(part1, 'pkg.name.class')
self.assertIsNone(part2)
def test_split_nested_class_from_key_lambda(self):
"""Tests that the helper works for common jdeps output (lambdas)"""
part1, part2 = class_dependency.split_nested_class_from_key(
'pkg.name.class$$Lambda$1')
self.assertEqual(part1, 'pkg.name.class')
self.assertEqual(part2, '$Lambda$1')
def test_split_nested_class_from_key_numeric(self):
"""Tests that the helper works for common jdeps output
(numeric class name, used for private nested classes)"""
part1, part2 = class_dependency.split_nested_class_from_key(
'pkg.name.class$1')
self.assertEqual(part1, 'pkg.name.class')
self.assertEqual(part2, '1')
class TestJavaClass(unittest.TestCase):
"""Unit tests for dependency_analysis.class_dependency.JavaClass."""
TEST_PKG = 'package'
TEST_CLS = 'class'
UNIQUE_KEY_1 = 'abc'
UNIQUE_KEY_2 = 'def'
def test_initialization(self):
"""Tests that the JavaClass's unique_key was initialized correctly."""
test_node = class_dependency.JavaClass(self.TEST_PKG, self.TEST_CLS)
self.assertEqual(test_node.name, f'{self.TEST_PKG}.{self.TEST_CLS}')
def test_equality(self):
"""Tests that two JavaClasses with the same package+class are equal."""
test_node = class_dependency.JavaClass(self.TEST_PKG, self.TEST_CLS)
equal_node = class_dependency.JavaClass(self.TEST_PKG, self.TEST_CLS)
self.assertEqual(test_node, equal_node)
def test_add_nested_class(self):
"""Tests adding a single nested class to this class."""
test_node = class_dependency.JavaClass(self.TEST_PKG, self.TEST_CLS)
test_node.add_nested_class(self.UNIQUE_KEY_1)
self.assertEqual(test_node.nested_classes, {self.UNIQUE_KEY_1})
def test_add_nested_class_multiple(self):
"""Tests adding multiple nested classes to this class."""
test_node = class_dependency.JavaClass(self.TEST_PKG, self.TEST_CLS)
test_node.add_nested_class(self.UNIQUE_KEY_1)
test_node.add_nested_class(self.UNIQUE_KEY_2)
self.assertEqual(test_node.nested_classes,
{self.UNIQUE_KEY_1, self.UNIQUE_KEY_2})
def test_add_nested_class_duplicate(self):
"""Tests that adding the same nested class twice will not dupe."""
test_node = class_dependency.JavaClass(self.TEST_PKG, self.TEST_CLS)
test_node.add_nested_class(self.UNIQUE_KEY_1)
test_node.add_nested_class(self.UNIQUE_KEY_1)
self.assertEqual(test_node.nested_classes, {self.UNIQUE_KEY_1})
class TestJavaClassDependencyGraph(unittest.TestCase):
"""Unit tests for
dependency_analysis.class_dependency.JavaClassDependencyGraph.
"""
def setUp(self):
"""Sets up a new JavaClassDependencyGraph."""
self.test_graph = class_dependency.JavaClassDependencyGraph()
def test_create_node_from_key(self):
"""Tests that a jdeps name is correctly parsed into package + class."""
created_node = self.test_graph.create_node_from_key(
'package.class$nested')
self.assertEqual(created_node._package, 'package')
self.assertEqual(created_node._class_name, 'class')
self.assertEqual(created_node.name, 'package.class')
def test_add_nested_class_to_key(self):
"""Tests adding a nested class to an existing node."""
self.test_graph.add_node_if_new('package.class')
self.test_graph.add_nested_class_to_key('package.class', 'nested')
added = self.test_graph.get_node_by_key('package.class')
self.assertEqual(len(added.nested_classes), 1)
self.assertIn('nested', added.nested_classes)
if __name__ == '__main__':
unittest.main()
...@@ -22,7 +22,7 @@ class Node(object): # pylint: disable=useless-object-inheritance ...@@ -22,7 +22,7 @@ class Node(object): # pylint: disable=useless-object-inheritance
self._outbound = set() self._outbound = set()
self._inbound = set() self._inbound = set()
def __eq__(self, other: "Node"): # pylint: disable=missing-function-docstring def __eq__(self, other: 'Node'): # pylint: disable=missing-function-docstring
return self._unique_key == other._unique_key return self._unique_key == other._unique_key
def __hash__(self): # pylint: disable=missing-function-docstring def __hash__(self): # pylint: disable=missing-function-docstring
...@@ -43,11 +43,11 @@ class Node(object): # pylint: disable=useless-object-inheritance ...@@ -43,11 +43,11 @@ class Node(object): # pylint: disable=useless-object-inheritance
"""A set of Nodes that this Node has a directed edge into.""" """A set of Nodes that this Node has a directed edge into."""
return self._outbound return self._outbound
def add_outbound(self, node: "Node"): def add_outbound(self, node: 'Node'):
"""Creates an edge from the current node to the provided node.""" """Creates an edge from the current node to the provided node."""
self._outbound.add(node) self._outbound.add(node)
def add_inbound(self, node: "Node"): def add_inbound(self, node: 'Node'):
"""Creates an edge from the provided node to the current node.""" """Creates an edge from the provided node to the current node."""
self._inbound.add(node) self._inbound.add(node)
......
#!/usr/bin/env python3
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for dependency_analysis.graph."""
import unittest
import graph
class TestNode(unittest.TestCase):
"""Unit tests for dependency_analysis.graph.Node."""
UNIQUE_KEY_1 = 'abc'
UNIQUE_KEY_2 = 'def'
def test_initialization(self):
"""Tests that the node was initialized correctly."""
test_node = graph.Node(self.UNIQUE_KEY_1)
self.assertEqual(test_node.name, self.UNIQUE_KEY_1)
self.assertEqual(len(test_node.inbound), 0)
self.assertEqual(len(test_node.outbound), 0)
def test_equality(self):
"""Tests that two nodes with the same unique keys are equal."""
test_node = graph.Node(self.UNIQUE_KEY_1)
equal_node = graph.Node(self.UNIQUE_KEY_1)
self.assertEqual(test_node, equal_node)
def test_add_outbound(self):
"""Tests adding a single outbound edge from the node."""
begin_node = graph.Node(self.UNIQUE_KEY_1)
end_node = graph.Node(self.UNIQUE_KEY_2)
begin_node.add_outbound(end_node)
self.assertEqual(begin_node.outbound, {end_node})
def test_add_outbound_duplicate(self):
"""Tests that adding the same outbound edge twice will not dupe."""
begin_node = graph.Node(self.UNIQUE_KEY_1)
end_node = graph.Node(self.UNIQUE_KEY_2)
begin_node.add_outbound(end_node)
begin_node.add_outbound(end_node)
self.assertEqual(begin_node.outbound, {end_node})
def test_add_outbound_self(self):
"""Tests adding an circular outbound edge to the node itself."""
test_node = graph.Node(self.UNIQUE_KEY_1)
test_node.add_outbound(test_node)
self.assertEqual(test_node.outbound, {test_node})
def test_add_inbound(self):
"""Tests adding a single inbound edge to the node."""
begin_node = graph.Node(self.UNIQUE_KEY_1)
end_node = graph.Node(self.UNIQUE_KEY_2)
end_node.add_inbound(begin_node)
self.assertEqual(end_node.inbound, {begin_node})
def test_add_inbound_duplicate(self):
"""Tests that adding the same inbound edge twice will not dupe."""
begin_node = graph.Node(self.UNIQUE_KEY_1)
end_node = graph.Node(self.UNIQUE_KEY_2)
end_node.add_inbound(begin_node)
end_node.add_inbound(begin_node)
self.assertEqual(end_node.inbound, {begin_node})
def test_add_inbound_self(self):
"""Tests adding an circular inbound edge from the node itself."""
test_node = graph.Node(self.UNIQUE_KEY_1)
test_node.add_inbound(test_node)
self.assertEqual(test_node.inbound, {test_node})
class TestGraph(unittest.TestCase):
"""Unit tests for dependency_analysis.graph.Graph."""
UNIQUE_KEY_1 = 'abc'
UNIQUE_KEY_2 = 'def'
def setUp(self):
"""Sets up a new graph object."""
self.test_graph = graph.Graph()
def test_initialization(self):
"""Tests that the graph was initialized correctly."""
self.assertEqual(self.test_graph.num_nodes, 0)
self.assertEqual(self.test_graph.num_edges, 0)
def test_get_node_exists(self):
"""Tests getting a node that we know exists in the graph."""
self.test_graph.add_node_if_new(self.UNIQUE_KEY_1)
self.assertIsNotNone(self.test_graph.get_node_by_key(
self.UNIQUE_KEY_1))
def test_get_node_does_not_exist(self):
"""Tests getting a node that we know does not exist in the graph."""
self.assertIsNone(self.test_graph.get_node_by_key(self.UNIQUE_KEY_1))
def test_add_nodes(self):
"""Tests adding two different nodes to the graph."""
self.test_graph.add_node_if_new(self.UNIQUE_KEY_1)
self.test_graph.add_node_if_new(self.UNIQUE_KEY_2)
self.assertEqual(self.test_graph.num_nodes, 2)
def test_add_nodes_duplicate(self):
"""Tests adding the same node twice to the graph."""
self.test_graph.add_node_if_new(self.UNIQUE_KEY_1)
self.test_graph.add_node_if_new(self.UNIQUE_KEY_1)
self.assertEqual(self.test_graph.num_nodes, 1)
def test_add_edge(self):
"""Tests adding a new edge to the graph."""
self.test_graph.add_node_if_new(self.UNIQUE_KEY_1)
self.test_graph.add_node_if_new(self.UNIQUE_KEY_2)
self.test_graph.add_edge_if_new(self.UNIQUE_KEY_1, self.UNIQUE_KEY_2)
self.assertEqual(self.test_graph.num_edges, 1)
node1 = self.test_graph.get_node_by_key(self.UNIQUE_KEY_1)
node2 = self.test_graph.get_node_by_key(self.UNIQUE_KEY_2)
self.assertEqual(node2.inbound, {node1})
self.assertEqual(node1.outbound, {node2})
def test_add_edge_double_sided(self):
"""Tests adding a bidirectional edge to the graph."""
self.test_graph.add_node_if_new(self.UNIQUE_KEY_1)
self.test_graph.add_node_if_new(self.UNIQUE_KEY_2)
self.test_graph.add_edge_if_new(self.UNIQUE_KEY_1, self.UNIQUE_KEY_2)
self.test_graph.add_edge_if_new(self.UNIQUE_KEY_2, self.UNIQUE_KEY_1)
self.assertEqual(self.test_graph.num_edges, 2)
node1 = self.test_graph.get_node_by_key(self.UNIQUE_KEY_1)
node2 = self.test_graph.get_node_by_key(self.UNIQUE_KEY_2)
self.assertEqual(node1.inbound, {node2})
self.assertEqual(node1.outbound, {node2})
self.assertEqual(node2.inbound, {node1})
self.assertEqual(node2.outbound, {node1})
def test_add_edge_duplicate(self):
"""Tests adding a duplicate edge to the graph."""
self.test_graph.add_node_if_new(self.UNIQUE_KEY_1)
self.test_graph.add_node_if_new(self.UNIQUE_KEY_2)
self.test_graph.add_edge_if_new(self.UNIQUE_KEY_1, self.UNIQUE_KEY_2)
self.test_graph.add_edge_if_new(self.UNIQUE_KEY_1, self.UNIQUE_KEY_2)
self.assertEqual(self.test_graph.num_edges, 1)
def test_add_edge_nodes_do_not_exist(self):
"""Tests adding a new edge to a graph without the edge's nodes."""
self.test_graph.add_edge_if_new(self.UNIQUE_KEY_1, self.UNIQUE_KEY_2)
self.assertEqual(self.test_graph.num_edges, 1)
self.assertIsNotNone(self.test_graph.get_node_by_key(
self.UNIQUE_KEY_1))
self.assertIsNotNone(self.test_graph.get_node_by_key(
self.UNIQUE_KEY_2))
if __name__ == '__main__':
unittest.main()
...@@ -10,44 +10,50 @@ import subprocess ...@@ -10,44 +10,50 @@ import subprocess
import class_dependency import class_dependency
SRC_PATH = pathlib.Path(__file__).resolve().parents[3] # src/ SRC_PATH = pathlib.Path(__file__).resolve().parents[3] # src/
JDEPS_PATH = SRC_PATH.joinpath("third_party/jdk/current/bin/jdeps") JDEPS_PATH = SRC_PATH.joinpath('third_party/jdk/current/bin/jdeps')
def class_is_interesting(name: str): def class_is_interesting(name: str):
"""Checks if a jdeps class is a class we are actually interested in.""" """Checks if a jdeps class is a class we are actually interested in."""
if name.startswith("org.chromium."): if name.startswith('org.chromium.'):
return True return True
return False return False
class JavaClassJdepsParser(object): # pylint: disable=useless-object-inheritance class JavaClassJdepsParser(object): # pylint: disable=useless-object-inheritance
"""A parser for jdeps class-level dependency output.""" """A parser for jdeps class-level dependency output.
Attributes:
graph: The dependency graph of the jdeps output. Initialized as empty
and updated using parse_raw_jdeps_output.
"""
def __init__(self): # pylint: disable=missing-function-docstring def __init__(self): # pylint: disable=missing-function-docstring
self._graph = class_dependency.JavaClassDependencyGraph() self._graph = class_dependency.JavaClassDependencyGraph()
def parse_raw_jdeps_output(self, jdeps_output: str): @property
"""Parses the entirety of the jdeps output.""" def graph(self):
for line in jdeps_output.split("\n"): """The dependency graph of the jdeps output.
self.parse_line(line)
def get_resulting_graph(self):
"""Returns the dependency graph of the parsed output.
parse_raw_jdeps_output should be called before this method. Initialized as empty and updated using parse_raw_jdeps_output.
""" """
return self._graph return self._graph
def parse_raw_jdeps_output(self, jdeps_output: str):
"""Parses the entirety of the jdeps output."""
for line in jdeps_output.split('\n'):
self.parse_line(line)
def parse_line(self, line: str): def parse_line(self, line: str):
"""Parses a line of jdeps output. """Parses a line of jdeps output.
The assumed format of the line starts with "name_1 -> name_2". The assumed format of the line starts with 'name_1 -> name_2'.
""" """
parsed = line.split() parsed = line.split()
if len(parsed) <= 3: if len(parsed) <= 3:
return return
if "not found" in line: if parsed[2] == 'not' and parsed[3] == 'found':
return return
if parsed[1] != "->": if parsed[1] != '->':
return return
dep_from = parsed[0] dep_from = parsed[0]
...@@ -74,7 +80,7 @@ class JavaClassJdepsParser(object): # pylint: disable=useless-object-inheritanc ...@@ -74,7 +80,7 @@ class JavaClassJdepsParser(object): # pylint: disable=useless-object-inheritanc
def run_jdeps(filepath: str): def run_jdeps(filepath: str):
"""Runs jdeps on the given filepath and returns the output.""" """Runs jdeps on the given filepath and returns the output."""
jdeps_res = subprocess.run([JDEPS_PATH, "-R", "-verbose:class", filepath], jdeps_res = subprocess.run([JDEPS_PATH, '-R', '-verbose:class', filepath],
capture_output=True, capture_output=True,
text=True, text=True,
check=True) check=True)
...@@ -89,18 +95,18 @@ def main(): ...@@ -89,18 +95,18 @@ def main():
constructed graph, but currently we just print its number of nodes/edges. constructed graph, but currently we just print its number of nodes/edges.
""" """
arg_parser = argparse.ArgumentParser( arg_parser = argparse.ArgumentParser(
description="Run jdeps and process output") description='Run jdeps and process output')
arg_parser.add_argument("filepath", help="Path of the JAR to run jdeps on") arg_parser.add_argument('filepath', help='Path of the JAR to run jdeps on')
arguments = arg_parser.parse_args() arguments = arg_parser.parse_args()
raw_jdeps_output = run_jdeps(arguments.filepath) raw_jdeps_output = run_jdeps(arguments.filepath)
jdeps_parser = JavaClassJdepsParser() jdeps_parser = JavaClassJdepsParser()
jdeps_parser.parse_raw_jdeps_output(raw_jdeps_output) jdeps_parser.parse_raw_jdeps_output(raw_jdeps_output)
graph = jdeps_parser.get_resulting_graph() graph = jdeps_parser.graph
print(f"Parsed graph, got {graph.num_nodes} nodes " print(f'Parsed graph, got {graph.num_nodes} nodes '
f"and {graph.num_edges} edges.") f'and {graph.num_edges} edges.')
if __name__ == "__main__": if __name__ == '__main__':
main() main()
#!/usr/bin/env python3
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for dependency_analysis.process_jdeps."""
import unittest
import process_jdeps
class TestHelperFunctions(unittest.TestCase):
"""Unit tests for module-level helper functions."""
def test_class_is_interesting(self):
"""Tests that the helper identifies a valid Chromium class name."""
self.assertTrue(
process_jdeps.class_is_interesting(
'org.chromium.chrome.browser.Foo'))
def test_class_is_interesting_longer(self):
"""Tests that the helper identifies a valid Chromium class name."""
self.assertTrue(
process_jdeps.class_is_interesting(
'org.chromium.chrome.browser.foo.Bar'))
def test_class_is_interesting_negative(self):
"""Tests that the helper ignores a non-Chromium class name."""
self.assertFalse(
process_jdeps.class_is_interesting(
'org.notchromium.chrome.browser.Foo'))
def test_class_is_interesting_not_interesting(self):
"""Tests that the helper ignores a builtin class name."""
self.assertFalse(
process_jdeps.class_is_interesting('java.lang.Object'))
class TestJavaClassJdepsParser(unittest.TestCase):
"""Unit tests for
dependency_analysis.class_dependency.JavaClassJdepsParser.
"""
def setUp(self):
"""Sets up a new JavaClassJdepsParser."""
self.parser = process_jdeps.JavaClassJdepsParser()
def test_parse_line(self):
"""Tests that new nodes + edges are added after a successful parse."""
self.parser.parse_line(
'org.chromium.a -> org.chromium.b org.chromium.c')
self.assertEqual(self.parser.graph.num_nodes, 2)
self.assertEqual(self.parser.graph.num_edges, 1)
def test_parse_line_not_interesting(self):
"""Tests that nothing is changed if there is an uninteresting class."""
self.parser.parse_line('org.chromium.a -> b c')
self.assertEqual(self.parser.graph.num_nodes, 0)
self.assertEqual(self.parser.graph.num_edges, 0)
def test_parse_line_too_short(self):
"""Tests that nothing is changed if the line is too short."""
self.parser.parse_line('org.chromium.a -> b')
self.assertEqual(self.parser.graph.num_nodes, 0)
self.assertEqual(self.parser.graph.num_edges, 0)
def test_parse_line_not_found(self):
"""Tests that nothing is changed if the line contains `not found`
as the second class.
"""
self.parser.parse_line('org.chromium.a -> not found')
self.assertEqual(self.parser.graph.num_nodes, 0)
self.assertEqual(self.parser.graph.num_edges, 0)
def test_parse_line_empty_string(self):
"""Tests that nothing is changed if the line is empty."""
self.parser.parse_line('')
self.assertEqual(self.parser.graph.num_nodes, 0)
self.assertEqual(self.parser.graph.num_edges, 0)
def test_parse_line_bad_input(self):
"""Tests that nothing is changed if the line is nonsensical"""
self.parser.parse_line('bad_input')
self.assertEqual(self.parser.graph.num_nodes, 0)
self.assertEqual(self.parser.graph.num_edges, 0)
if __name__ == '__main__':
unittest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment