am b859e6af: Merge "Parallelize Test Description Generalization" into gingerbread

* commit 'b859e6afb1ec3128fecbdf455ca32aca032dc187':
  Parallelize Test Description Generalization
diff --git a/tools/utils/buildCts.py b/tools/utils/buildCts.py
index 35a3a11..4daf48e 100755
--- a/tools/utils/buildCts.py
+++ b/tools/utils/buildCts.py
@@ -23,7 +23,7 @@
 import sys
 import xml.dom.minidom as dom
 from cts import tools
-
+from multiprocessing import Pool
 
 def GetSubDirectories(root):
   """Return all directories under the given root directory."""
@@ -72,173 +72,24 @@
     self.test_repository = os.path.join(self.out_dir, 'repository/testcases')
     self.plan_repository = os.path.join(self.out_dir, 'repository/plans')
 
-  def __LogGenerateDescription(self, name):
-    print 'Generating test description for package %s' % name
-
-  def RunDescriptionGeneratorDoclet(self, source_root, output_file):
-    """Generate a test package description by running the DescriptionGenerator doclet.
-
-    Args:
-      source_root: Directory under which tests should be searched.
-      output_file: Name of the file where the description gets written.
-
-    Returns:
-      The exit code of the DescriptionGenerator doclet run.
-    """
-    # Make sure sourceRoot is relative to  self.android_root
-    source_root = self.RelPath(source_root, self.android_root)
-
-    # To determine whether a class is a JUnit test, the Doclet needs to have all intermediate
-    # subclasses of TestCase as well as the JUnit framework itself on the source path.
-    # Annotation classes are also required, since test annotations go into the description.
-    source_path = [
-        'frameworks/base/core/java',            # android test classes
-        'frameworks/base/test-runner/src',      # test runner
-        'libcore/junit/src/main/java',          # junit classes
-        'development/tools/hosttestlib/src',    # hosttestlib TestCase extensions
-        'libcore/dalvik/src/main/java',         # test annotations
-        'cts/tests/src',                        # cts test stubs
-        source_root                             # the source for this package
-    ]
-    source_path = [os.path.join(self.android_root, x) for x in source_path]
-    cmd = ('javadoc -o %s -J-Xmx512m -quiet -doclet DescriptionGenerator -docletpath %s'
-           ' -sourcepath %s ') % (output_file, self.doclet_path, ':'.join(source_path))
-    sources = []
-
-    def AddFile(sources, folder, names):
-      """Find *.java."""
-      sources.extend([os.path.join(folder, name) for name in names if name.endswith('.java')])
-
-    os.path.walk(os.path.join(self.android_root, source_root), AddFile, sources)
-    cmd += ' '.join(sources)
-    proc = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
-    # read and discard any output
-    proc.communicate()
-    # wait for process to terminate and return exit value
-    return proc.wait()
-
-  def GenerateSignatureCheckDescription(self):
-    """Generate the test description for the signature check."""
-    self.__LogGenerateDescription('android.tests.sigtest')
-    package = tools.TestPackage('SignatureTest', 'android.tests.sigtest')
-    package.AddAttribute('appNameSpace', 'android.tests.sigtest')
-    package.AddAttribute('signatureCheck', 'true')
-    package.AddAttribute('runner', '.InstrumentationRunner')
-    package.AddTest('android.tests.sigtest.SignatureTest.signatureTest')
-    description = open(os.path.join(self.test_repository, 'SignatureTest.xml'), 'w')
-    package.WriteDescription(description)
-    description.close()
-
-  def GenerateReferenceAppDescription(self):
-    """Generate the test description for the reference app tests."""
-    self.__LogGenerateDescription('android.apidemos.cts')
-    package = tools.TestPackage('ApiDemosReferenceTest', 'android.apidemos.cts')
-    package.AddAttribute('appNameSpace', 'android.apidemos.cts')
-    package.AddAttribute('packageToTest', 'com.example.android.apis')
-    package.AddAttribute('apkToTestName', 'ApiDemos')
-    package.AddAttribute('runner', 'android.test.InstrumentationTestRunner')
-    package.AddAttribute('referenceAppTest', 'true')
-    package.AddTest('android.apidemos.cts.ApiDemosTest.testNumberOfItemsInListView')
-    description = open(os.path.join(self.test_repository, 'ApiDemosReferenceTest.xml'), 'w')
-    package.WriteDescription(description)
-    description.close()
-
-  def GenerateAppSecurityDescription(self):
-    """Generate the test description for the application security tests."""
-    test_root = 'cts/tests/appsecurity-tests'
-    makefile_name = os.path.join(test_root, 'Android.mk')
-    makefile_vars = GetMakeFileVars(makefile_name)
-    name = makefile_vars['LOCAL_MODULE']
-    package_name = 'android.tests.appsecurity'
-    self.__LogGenerateDescription(package_name)
-    temp_desc = os.path.join(self.temp_dir, 'description.xml')
-    self.RunDescriptionGeneratorDoclet(os.path.join(test_root, 'src'), temp_desc)
-    doc = dom.parse(temp_desc)
-    test_description = doc.getElementsByTagName('TestPackage')[0]
-    test_description.setAttribute('name', package_name)
-    test_description.setAttribute('appPackageName', package_name)
-    test_description.setAttribute('hostSideOnly', 'true')
-    test_description.setAttribute('jarPath', name + '.jar')
-    description = open(os.path.join(self.test_repository, package_name + '.xml'), 'w')
-    doc.writexml(description, addindent='    ', encoding='UTF-8')
-    description.close()
-
-  @staticmethod
-  def RelPath(path, start=os.getcwd()):
-    """Get a relative version of a path.
-
-    This is equivalent to os.path.relpath, which is only available since Python 2.6.
-
-    Args:
-      path: The path to transform.
-      start: The base path. Defaults to the current working directory.
-
-    Returns:
-      A transformed path that is relative to start.
-    """
-    path_dirs = os.path.abspath(path).split(os.path.sep)
-    start_dirs = os.path.abspath(start).split(os.path.sep)
-
-    num_common = len(os.path.commonprefix([start_dirs, path_dirs]))
-
-    result_dirs = ['..'] * (len(start_dirs) - num_common) + path_dirs[num_common:]
-    if result_dirs:
-      return os.path.join(*result_dirs)
-    return start
-
   def GenerateTestDescriptions(self):
     """Generate test descriptions for all packages."""
+    pool = Pool(processes=16)
+
     # individually generate descriptions not following conventions
-    self.GenerateSignatureCheckDescription()
-    self.GenerateReferenceAppDescription()
-    self.GenerateAppSecurityDescription()
+    pool.apply_async(GenerateSignatureCheckDescription, [self.test_repository])
+    pool.apply_async(GenerateReferenceAppDescription, [self.test_repository])
+    pool.apply_async(GenerateAppSecurityDescription, [self.temp_dir,
+        self.test_repository, self.android_root, self.doclet_path])
 
     # generate test descriptions for android tests
     android_packages = GetSubDirectories(self.test_root)
     for package in android_packages:
-      app_package_name = 'android.' + package
-      package_root = os.path.join(self.test_root, package)
+      pool.apply_async(GenerateTestDescription, [self.test_root, self.temp_dir,
+          self.test_repository, self.android_root, self.doclet_path, package])
 
-      makefile_name = os.path.join(package_root, 'Android.mk')
-      if not os.path.exists(makefile_name):
-        print 'Skipping directory "%s" due to missing Android.mk' % package_root
-        continue
-      makefile_vars = GetMakeFileVars(makefile_name)
-
-      manifest_name = os.path.join(package_root, 'AndroidManifest.xml')
-      if not os.path.exists(manifest_name):
-        print 'Skipping directory "%s" due to missing AndroidManifest.xml' % package_root
-        continue
-      manifest = tools.XmlFile(manifest_name)
-
-      self.__LogGenerateDescription(app_package_name)
-
-      # Run the description generator doclet to get the test package structure
-      # TODO: The Doclet does not currently add all required attributes. Instead of rewriting
-      # the document below, additional attributes should be passed to the Doclet as arguments.
-      temp_desc = os.path.join(self.temp_dir, 'description.xml')
-      self.RunDescriptionGeneratorDoclet(package_root, temp_desc)
-
-      # obtain missing attribute values from the makefile and manifest
-      package_name = makefile_vars['LOCAL_PACKAGE_NAME']
-      runner = manifest.GetAndroidAttr('instrumentation', 'name')
-      target_package = manifest.GetAndroidAttr('instrumentation', 'targetPackage')
-      target_binary_name = makefile_vars.get('LOCAL_INSTRUMENTATION_FOR')
-
-      # add them to the document
-      doc = dom.parse(temp_desc)
-      test_description = doc.getElementsByTagName('TestPackage')[0]
-      test_description.setAttribute('name', package_name)
-      test_description.setAttribute('runner', runner)
-      test_package = manifest.GetAttr('manifest', 'package')
-      test_description.setAttribute('appNameSpace', test_package)
-      test_description.setAttribute('appPackageName', app_package_name)
-      if not test_package == target_package:
-        test_description.setAttribute('targetNameSpace', target_package)
-        test_description.setAttribute('targetBinaryName', target_binary_name)
-      description = open(os.path.join(self.test_repository, package_name + '.xml'), 'w')
-      doc.writexml(description, addindent='    ', encoding='UTF-8')
-      description.close()
+    pool.close()
+    pool.join()
 
   def __WritePlan(self, plan, plan_name):
     print 'Generating test plan %s' % plan_name
@@ -285,6 +136,170 @@
     plan.Include(r'android\.tests\.appsecurity')
     self.__WritePlan(plan, 'AppSecurity')
 
+def LogGenerateDescription(name):
+  print 'Generating test description for package %s' % name
+
+def GenerateSignatureCheckDescription(test_repository):
+  """Generate the test description for the signature check."""
+  LogGenerateDescription('android.tests.sigtest')
+  package = tools.TestPackage('SignatureTest', 'android.tests.sigtest')
+  package.AddAttribute('appNameSpace', 'android.tests.sigtest')
+  package.AddAttribute('signatureCheck', 'true')
+  package.AddAttribute('runner', '.InstrumentationRunner')
+  package.AddTest('android.tests.sigtest.SignatureTest.signatureTest')
+  description = open(os.path.join(test_repository, 'SignatureTest.xml'), 'w')
+  package.WriteDescription(description)
+  description.close()
+
+def GenerateReferenceAppDescription(test_repository):
+  """Generate the test description for the reference app tests."""
+  LogGenerateDescription('android.apidemos.cts')
+  package = tools.TestPackage('ApiDemosReferenceTest', 'android.apidemos.cts')
+  package.AddAttribute('appNameSpace', 'android.apidemos.cts')
+  package.AddAttribute('packageToTest', 'com.example.android.apis')
+  package.AddAttribute('apkToTestName', 'ApiDemos')
+  package.AddAttribute('runner', 'android.test.InstrumentationTestRunner')
+  package.AddAttribute('referenceAppTest', 'true')
+  package.AddTest('android.apidemos.cts.ApiDemosTest.testNumberOfItemsInListView')
+  description = open(os.path.join(test_repository, 'ApiDemosReferenceTest.xml'), 'w')
+  package.WriteDescription(description)
+  description.close()
+
+def GenerateAppSecurityDescription(temp_dir, test_repository, android_root, doclet_path):
+  """Generate the test description for the application security tests."""
+  test_root = 'cts/tests/appsecurity-tests'
+  makefile_name = os.path.join(test_root, 'Android.mk')
+  makefile_vars = GetMakeFileVars(makefile_name)
+  name = makefile_vars['LOCAL_MODULE']
+  package_name = 'android.tests.appsecurity'
+  LogGenerateDescription(package_name)
+  temp_desc = os.path.join(temp_dir, 'description.xml')
+  RunDescriptionGeneratorDoclet(android_root, doclet_path,
+      os.path.join(test_root, 'src'), temp_desc)
+  doc = dom.parse(temp_desc)
+  test_description = doc.getElementsByTagName('TestPackage')[0]
+  test_description.setAttribute('name', package_name)
+  test_description.setAttribute('appPackageName', package_name)
+  test_description.setAttribute('hostSideOnly', 'true')
+  test_description.setAttribute('jarPath', name + '.jar')
+  description = open(os.path.join(test_repository, package_name + '.xml'), 'w')
+  doc.writexml(description, addindent='    ', encoding='UTF-8')
+  description.close()
+
+
+def GenerateTestDescription(test_root, temp_dir, test_repository, android_root,
+                            doclet_path, package):
+
+  app_package_name = 'android.' + package
+  package_root = os.path.join(test_root, package)
+
+  makefile_name = os.path.join(package_root, 'Android.mk')
+  if not os.path.exists(makefile_name):
+    print 'Skipping directory "%s" due to missing Android.mk' % package_root
+    return
+  makefile_vars = GetMakeFileVars(makefile_name)
+
+  manifest_name = os.path.join(package_root, 'AndroidManifest.xml')
+  if not os.path.exists(manifest_name):
+    print 'Skipping directory "%s" due to missing AndroidManifest.xml' % package_root
+    return
+  manifest = tools.XmlFile(manifest_name)
+
+  LogGenerateDescription(app_package_name)
+
+  # Run the description generator doclet to get the test package structure
+  # TODO: The Doclet does not currently add all required attributes. Instead of rewriting
+  # the document below, additional attributes should be passed to the Doclet as arguments.
+  temp_desc = os.path.join(temp_dir, app_package_name + '-description.xml')
+
+  RunDescriptionGeneratorDoclet(android_root, doclet_path, package_root, temp_desc)
+
+  # obtain missing attribute values from the makefile and manifest
+  package_name = makefile_vars['LOCAL_PACKAGE_NAME']
+  runner = manifest.GetAndroidAttr('instrumentation', 'name')
+  target_package = manifest.GetAndroidAttr('instrumentation', 'targetPackage')
+  target_binary_name = makefile_vars.get('LOCAL_INSTRUMENTATION_FOR')
+
+  # add them to the document
+  doc = dom.parse(temp_desc)
+  test_description = doc.getElementsByTagName('TestPackage')[0]
+  test_description.setAttribute('name', package_name)
+  test_description.setAttribute('runner', runner)
+  test_package = manifest.GetAttr('manifest', 'package')
+  test_description.setAttribute('appNameSpace', test_package)
+  test_description.setAttribute('appPackageName', app_package_name)
+  if not test_package == target_package:
+    test_description.setAttribute('targetNameSpace', target_package)
+    test_description.setAttribute('targetBinaryName', target_binary_name)
+  description = open(os.path.join(test_repository, package_name + '.xml'), 'w')
+  doc.writexml(description, addindent='    ', encoding='UTF-8')
+  description.close()
+
+def RunDescriptionGeneratorDoclet(android_root, doclet_path, source_root, output_file):
+  """Generate a test package description by running the DescriptionGenerator doclet.
+
+  Args:
+    android_root: Root directory of the Android source tree.
+    doclet_path: Class path where the DescriptionGenerator doclet can be found.
+    source_root: Directory under which tests should be searched.
+    output_file: Name of the file where the description gets written.
+
+  Returns:
+    The exit code of the DescriptionGenerator doclet run.
+  """
+  # Make sure sourceRoot is relative to  self.android_root
+  source_root = RelPath(source_root, android_root)
+
+  # To determine whether a class is a JUnit test, the Doclet needs to have all intermediate
+  # subclasses of TestCase as well as the JUnit framework itself on the source path.
+  # Annotation classes are also required, since test annotations go into the description.
+  source_path = [
+      'frameworks/base/core/java',            # android test classes
+      'frameworks/base/test-runner/src',      # test runner
+      'libcore/junit/src/main/java',          # junit classes
+      'development/tools/hosttestlib/src',    # hosttestlib TestCase extensions
+      'libcore/dalvik/src/main/java',         # test annotations
+      'cts/tests/src',                        # cts test stubs
+      source_root                             # the source for this package
+  ]
+  source_path = [os.path.join(android_root, x) for x in source_path]
+  cmd = ('javadoc -o %s -J-Xmx512m -quiet -doclet DescriptionGenerator -docletpath %s'
+         ' -sourcepath %s ') % (output_file, doclet_path, ':'.join(source_path))
+  sources = []
+
+  def AddFile(sources, folder, names):
+    """Find *.java."""
+    sources.extend([os.path.join(folder, name) for name in names if name.endswith('.java')])
+
+  os.path.walk(os.path.join(android_root, source_root), AddFile, sources)
+  cmd += ' '.join(sources)
+  proc = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
+  # read and discard any output
+  proc.communicate()
+  # wait for process to terminate and return exit value
+  return proc.wait()
+
+def RelPath(path, start=os.getcwd()):
+  """Get a relative version of a path.
+
+  This is equivalent to os.path.relpath, which is only available since Python 2.6.
+
+  Args:
+    path: The path to transform.
+    start: The base path. Defaults to the current working directory.
+
+  Returns:
+    A transformed path that is relative to start.
+  """
+  path_dirs = os.path.abspath(path).split(os.path.sep)
+  start_dirs = os.path.abspath(start).split(os.path.sep)
+
+  num_common = len(os.path.commonprefix([start_dirs, path_dirs]))
+
+  result_dirs = ['..'] * (len(start_dirs) - num_common) + path_dirs[num_common:]
+  if result_dirs:
+    return os.path.join(*result_dirs)
+  return start
 
 if __name__ == '__main__':
   builder = CtsBuilder(sys.argv)