• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2
3# Copyright (C) 2009 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Module for generating CTS test descriptions and test plans."""
18
19import glob
20import os
21import re
22import subprocess
23import sys
24import xml.dom.minidom as dom
25from cts import tools
26
27
28def GetSubDirectories(root):
29  """Return all directories under the given root directory."""
30  return [x for x in os.listdir(root) if os.path.isdir(os.path.join(root, x))]
31
32
33def GetMakeFileVars(makefile_path):
34  """Extracts variable definitions from the given make file.
35
36  Args:
37    makefile_path: Path to the make file.
38
39  Returns:
40    A dictionary mapping variable names to their assigned value.
41  """
42  result = {}
43  pattern = re.compile(r'^\s*([^:#=\s]+)\s*:=\s*(.*?[^\\])$', re.MULTILINE + re.DOTALL)
44  stream = open(makefile_path, 'r')
45  content = stream.read()
46  for match in pattern.finditer(content):
47    result[match.group(1)] = match.group(2)
48  stream.close()
49  return result
50
51
52class CtsBuilder(object):
53  """Main class for generating test descriptions and test plans."""
54
55  def __init__(self, argv):
56    """Initialize the CtsBuilder from command line arguments."""
57    if not len(argv) == 6:
58      print 'Usage: %s <testRoot> <ctsOutputDir> <tempDir> <androidRootDir> <docletPath>' % argv[0]
59      print ''
60      print 'testRoot:       Directory under which to search for CTS tests.'
61      print 'ctsOutputDir:   Directory in which the CTS repository should be created.'
62      print 'tempDir:        Directory to use for storing temporary files.'
63      print 'androidRootDir: Root directory of the Android source tree.'
64      print 'docletPath:     Class path where the DescriptionGenerator doclet can be found.'
65      sys.exit(1)
66    self.test_root = sys.argv[1]
67    self.out_dir = sys.argv[2]
68    self.temp_dir = sys.argv[3]
69    self.android_root = sys.argv[4]
70    self.doclet_path = sys.argv[5]
71
72    self.test_repository = os.path.join(self.out_dir, 'repository/testcases')
73    self.plan_repository = os.path.join(self.out_dir, 'repository/plans')
74
75  def __LogGenerateDescription(self, name):
76    print 'Generating test description for package %s' % name
77
78  def RunDescriptionGeneratorDoclet(self, source_root, output_file):
79    """Generate a test package description by running the DescriptionGenerator doclet.
80
81    Args:
82      source_root: Directory under which tests should be searched.
83      output_file: Name of the file where the description gets written.
84
85    Returns:
86      The exit code of the DescriptionGenerator doclet run.
87    """
88    # Make sure sourceRoot is relative to  self.android_root
89    source_root = self.RelPath(source_root, self.android_root)
90
91    # To determine whether a class is a JUnit test, the Doclet needs to have all intermediate
92    # subclasses of TestCase as well as the JUnit framework itself on the source path.
93    # Annotation classes are also required, since test annotations go into the description.
94    source_path = [
95        'frameworks/base/core/java',            # android test classes
96        'frameworks/base/test-runner',          # test runner
97        'dalvik/libcore/junit/src/main/java',   # junit classes
98        'development/tools/hosttestlib/src',    # hosttestlib TestCase extensions
99        'dalvik/libcore/dalvik/src/main/java',  # test annotations
100        'cts/tests/src',                        # cts test stubs
101        source_root                             # the source for this package
102    ]
103    source_path = [os.path.join(self.android_root, x) for x in source_path]
104    cmd = ('javadoc -o %s -J-Xmx512m -quiet -doclet DescriptionGenerator -docletpath %s'
105           ' -sourcepath %s ') % (output_file, self.doclet_path, ':'.join(source_path))
106    sources = []
107
108    def AddFile(sources, folder, names):
109      """Find *.java."""
110      sources.extend([os.path.join(folder, name) for name in names if name.endswith('.java')])
111
112    os.path.walk(os.path.join(self.android_root, source_root), AddFile, sources)
113    cmd += ' '.join(sources)
114    proc = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
115    # read and discard any output
116    proc.communicate()
117    # wait for process to terminate and return exit value
118    return proc.wait()
119
120  def GenerateSignatureCheckDescription(self):
121    """Generate the test description for the signature check."""
122    self.__LogGenerateDescription('android.tests.sigtest')
123    package = tools.TestPackage('SignatureTest', 'android.tests.sigtest')
124    package.AddAttribute('signatureCheck', 'true')
125    package.AddAttribute('runner', '.InstrumentationRunner')
126    package.AddTest('android.tests.sigtest.SignatureTest.signatureTest')
127    description = open(os.path.join(self.test_repository, 'SignatureTest.xml'), 'w')
128    package.WriteDescription(description)
129    description.close()
130
131  def GenerateReferenceAppDescription(self):
132    """Generate the test description for the reference app tests."""
133    self.__LogGenerateDescription('android.apidemos.cts')
134    package = tools.TestPackage('ApiDemosReferenceTest', 'android.apidemos.cts')
135    package.AddAttribute('packageToTest', 'com.example.android.apis')
136    package.AddAttribute('apkToTestName', 'ApiDemos')
137    package.AddAttribute('runner', 'android.test.InstrumentationTestRunner')
138    package.AddAttribute('referenceAppTest', 'true')
139    package.AddTest('android.apidemos.cts.ApiDemosTest.testNumberOfItemsInListView')
140    description = open(os.path.join(self.test_repository, 'ApiDemosReferenceTest.xml'), 'w')
141    package.WriteDescription(description)
142    description.close()
143
144  def GenerateAppSecurityDescription(self):
145    """Generate the test description for the application security tests."""
146    test_root = 'cts/tests/appsecurity-tests'
147    makefile_name = os.path.join(test_root, 'Android.mk')
148    makefile_vars = GetMakeFileVars(makefile_name)
149    name = makefile_vars['LOCAL_MODULE']
150    package_name = 'android.tests.appsecurity'
151    self.__LogGenerateDescription(package_name)
152    temp_desc = os.path.join(self.temp_dir, 'description.xml')
153    self.RunDescriptionGeneratorDoclet(os.path.join(test_root, 'src'), temp_desc)
154    doc = dom.parse(temp_desc)
155    test_description = doc.getElementsByTagName('TestPackage')[0]
156    test_description.setAttribute('name', package_name)
157    test_description.setAttribute('appPackageName', package_name)
158    test_description.setAttribute('hostSideOnly', 'true')
159    test_description.setAttribute('jarPath', name + '.jar')
160    description = open(os.path.join(self.test_repository, package_name + '.xml'), 'w')
161    doc.writexml(description, addindent='    ', encoding='UTF-8')
162    description.close()
163
164  @staticmethod
165  def RelPath(path, start=os.getcwd()):
166    """Get a relative version of a path.
167
168    This is equivalent to os.path.relpath, which is only available since Python 2.6.
169
170    Args:
171      path: The path to transform.
172      start: The base path. Defaults to the current working directory.
173
174    Returns:
175      A transformed path that is relative to start.
176    """
177    path_dirs = os.path.abspath(path).split(os.path.sep)
178    start_dirs = os.path.abspath(start).split(os.path.sep)
179
180    num_common = len(os.path.commonprefix([start_dirs, path_dirs]))
181
182    result_dirs = ['..'] * (len(start_dirs) - num_common) + path_dirs[num_common:]
183    if result_dirs:
184      return os.path.join(*result_dirs)
185    return start
186
187  def GenerateTestDescriptions(self):
188    """Generate test descriptions for all packages."""
189    # individually generate descriptions not following conventions
190    self.GenerateSignatureCheckDescription()
191    self.GenerateReferenceAppDescription()
192    self.GenerateAppSecurityDescription()
193
194    # generate test descriptions for android tests
195    android_packages = GetSubDirectories(self.test_root)
196    for package in android_packages:
197      app_package_name = 'android.' + package
198      package_root = os.path.join(self.test_root, package)
199
200      makefile_name = os.path.join(package_root, 'Android.mk')
201      if not os.path.exists(makefile_name):
202        print 'Skipping directory "%s" due to missing Android.mk' % package_root
203        continue
204      makefile_vars = GetMakeFileVars(makefile_name)
205
206      manifest_name = os.path.join(package_root, 'AndroidManifest.xml')
207      if not os.path.exists(manifest_name):
208        print 'Skipping directory "%s" due to missing AndroidManifest.xml' % package_root
209        continue
210      manifest = tools.XmlFile(manifest_name)
211
212      self.__LogGenerateDescription(app_package_name)
213
214      # Run the description generator doclet to get the test package structure
215      # TODO: The Doclet does not currently add all required attributes. Instead of rewriting
216      # the document below, additional attributes should be passed to the Doclet as arguments.
217      temp_desc = os.path.join(self.temp_dir, 'description.xml')
218      self.RunDescriptionGeneratorDoclet(package_root, temp_desc)
219
220      # obtain missing attribute values from the makefile and manifest
221      package_name = makefile_vars['LOCAL_PACKAGE_NAME']
222      runner = manifest.GetAndroidAttr('instrumentation', 'name')
223      target_package = manifest.GetAndroidAttr('instrumentation', 'targetPackage')
224      target_binary_name = makefile_vars.get('LOCAL_INSTRUMENTATION_FOR')
225
226      # add them to the document
227      doc = dom.parse(temp_desc)
228      test_description = doc.getElementsByTagName('TestPackage')[0]
229      test_description.setAttribute('name', package_name)
230      test_description.setAttribute('runner', runner)
231      test_package = manifest.GetAttr('manifest', 'package')
232      test_description.setAttribute('appNameSpace', test_package)
233      test_description.setAttribute('appPackageName', app_package_name)
234      if not test_package == target_package:
235        test_description.setAttribute('targetNameSpace', target_package)
236        test_description.setAttribute('targetBinaryName', target_binary_name)
237      description = open(os.path.join(self.test_repository, package_name + '.xml'), 'w')
238      doc.writexml(description, addindent='    ', encoding='UTF-8')
239      description.close()
240
241  def __WritePlan(self, plan, plan_name):
242    print 'Generating test plan %s' % plan_name
243    plan.Write(os.path.join(self.plan_repository, plan_name + '.xml'))
244
245  def GenerateTestPlans(self):
246    """Generate default test plans."""
247    # TODO: Instead of hard-coding the plans here, use a configuration file,
248    # such as test_defs.xml
249    packages = []
250    descriptions = sorted(glob.glob(os.path.join(self.test_repository, '*.xml')))
251    for description in descriptions:
252      doc = tools.XmlFile(description)
253      packages.append(doc.GetAttr('TestPackage', 'appPackageName'))
254
255    plan = tools.TestPlan(packages)
256    plan.Exclude('android\.performance.*')
257    self.__WritePlan(plan, 'CTS')
258    plan.Exclude(r'android\.tests\.sigtest')
259    plan.Exclude(r'android\.core.*')
260    self.__WritePlan(plan, 'Android')
261
262    plan = tools.TestPlan(packages)
263    plan.Include(r'android\.core\.tests.*')
264    self.__WritePlan(plan, 'Java')
265
266    plan = tools.TestPlan(packages)
267    plan.Include(r'android\.core\.vm-tests')
268    self.__WritePlan(plan, 'VM')
269
270    plan = tools.TestPlan(packages)
271    plan.Include(r'android\.tests\.sigtest')
272    self.__WritePlan(plan, 'Signature')
273
274    plan = tools.TestPlan(packages)
275    plan.Include(r'android\.apidemos\.cts')
276    self.__WritePlan(plan, 'RefApp')
277
278    plan = tools.TestPlan(packages)
279    plan.Include(r'android\.performance.*')
280    self.__WritePlan(plan, 'Performance')
281
282    plan = tools.TestPlan(packages)
283    plan.Include(r'android\.tests\.appsecurity')
284    self.__WritePlan(plan, 'AppSecurity')
285
286
287if __name__ == '__main__':
288  builder = CtsBuilder(sys.argv)
289  builder.GenerateTestDescriptions()
290  builder.GenerateTestPlans()
291