Make coverage work without test defs.

Change-Id: I946df038e97dc5c2f40a4610d4076e13ab6bde37
diff --git a/testrunner/make_tree.py b/testrunner/make_tree.py
new file mode 100644
index 0000000..c8bac17
--- /dev/null
+++ b/testrunner/make_tree.py
@@ -0,0 +1,116 @@
+#
+# Copyright 2012, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Data structure for processing makefiles."""
+
+import os
+
+import android_build
+import android_mk
+import errors
+
+class MakeNode(object):
+  """Represents single node in make tree."""
+
+  def __init__(self, name, parent):
+    self._name = name
+    self._children_map = {}
+    self._is_leaf = False
+    self._parent = parent
+    self._includes_submake = None
+    if parent:
+      self._path = os.path.join(parent._GetPath(), name)
+    else:
+      self._path = ""
+
+  def _AddPath(self, path_segs):
+    """Adds given path to this node.
+
+    Args:
+      path_segs: list of path segments
+    """
+    if not path_segs:
+      # done processing path
+      return self
+    current_seg = path_segs.pop(0)
+    child = self._children_map.get(current_seg)
+    if not child:
+      child = MakeNode(current_seg, self)
+      self._children_map[current_seg] = child
+    return child._AddPath(path_segs)
+
+  def _SetLeaf(self, is_leaf):
+    self._is_leaf = is_leaf
+
+  def _GetPath(self):
+    return self._path
+
+  def _DoesIncludesSubMake(self):
+    if self._includes_submake is None:
+      if self._is_leaf:
+        path = os.path.join(android_build.GetTop(), self._path)
+        mk_parser = android_mk.CreateAndroidMK(path)
+        self._includes_submake = mk_parser.IncludesMakefilesUnder()
+      else:
+        self._includes_submake = False
+    return self._includes_submake
+
+  def _DoesParentIncludeMe(self):
+    return self._parent and self._parent._DoesIncludesSubMake()
+
+  def _BuildPrunedMakeList(self, make_list):
+    if self._is_leaf and not self._DoesParentIncludeMe():
+      make_list.append(os.path.join(self._path, "Android.mk"))
+    for child in self._children_map.itervalues():
+      child._BuildPrunedMakeList(make_list)
+
+
+class MakeTree(MakeNode):
+  """Data structure for building a non-redundant set of Android.mk paths.
+
+  Used to collapse set of Android.mk files to use to prevent issuing make
+  command that include same module multiple times due to include rules.
+  """
+
+  def __init__(self):
+    super(MakeTree, self).__init__("", None)
+
+  def AddPath(self, path):
+    """Adds make directory path to tree.
+
+    Will have no effect if path is already included in make set.
+
+    Args:
+      path: filesystem path to directory to build, relative to build root.
+    """
+    path = os.path.normpath(path)
+    mk_path = os.path.join(android_build.GetTop(), path, "Android.mk")
+    if not os.path.isfile(mk_path):
+      raise errors.AbortError("%s does not exist" % mk_path)
+    path_segs = path.split(os.sep)
+    child = self._AddPath(path_segs)
+    child._SetLeaf(True)
+
+  def GetPrunedMakeList(self):
+    """Return as list of the minimum set of Android.mk files necessary to
+    build all leaf nodes in tree.
+    """
+    make_list = []
+    self._BuildPrunedMakeList(make_list)
+    return make_list
+
+  def IsEmpty(self):
+    return not self._children_map
+