blob: 86d870ec9645cdd0d52aeb091aac1a5a015d08b3 [file] [log] [blame]
Phil Dubachec19a572009-08-21 15:20:13 -07001#!/usr/bin/python
Phil Dubach0d6ef062009-08-12 18:13:16 -07002
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
Brian Muramatsuf5cebe52010-03-24 18:09:57 -070096 'frameworks/base/test-runner/src', # test runner
Phil Dubach0d6ef062009-08-12 18:13:16 -070097 'dalvik/libcore/junit/src/main/java', # junit classes
Phil Dubach60680482009-08-19 10:13:23 -070098 'development/tools/hosttestlib/src', # hosttestlib TestCase extensions
Phil Dubach0d6ef062009-08-12 18:13:16 -070099 'dalvik/libcore/dalvik/src/main/java', # test annotations
Brian Muramatsu7bc0e8e2010-08-24 12:24:18 -0700100 'cts/libs/annotation/src', # cts annotations
Phil Dubach0d6ef062009-08-12 18:13:16 -0700101 'cts/tests/src', # cts test stubs
102 source_root # the source for this package
103 ]
104 source_path = [os.path.join(self.android_root, x) for x in source_path]
105 cmd = ('javadoc -o %s -J-Xmx512m -quiet -doclet DescriptionGenerator -docletpath %s'
106 ' -sourcepath %s ') % (output_file, self.doclet_path, ':'.join(source_path))
107 sources = []
108
109 def AddFile(sources, folder, names):
110 """Find *.java."""
111 sources.extend([os.path.join(folder, name) for name in names if name.endswith('.java')])
112
113 os.path.walk(os.path.join(self.android_root, source_root), AddFile, sources)
114 cmd += ' '.join(sources)
115 proc = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
116 # read and discard any output
117 proc.communicate()
118 # wait for process to terminate and return exit value
119 return proc.wait()
120
121 def GenerateSignatureCheckDescription(self):
122 """Generate the test description for the signature check."""
123 self.__LogGenerateDescription('android.tests.sigtest')
124 package = tools.TestPackage('SignatureTest', 'android.tests.sigtest')
Brian Muramatsu94b366e2010-04-06 15:41:41 -0700125 package.AddAttribute('appNameSpace', 'android.tests.sigtest')
Phil Dubach0d6ef062009-08-12 18:13:16 -0700126 package.AddAttribute('signatureCheck', 'true')
127 package.AddAttribute('runner', '.InstrumentationRunner')
128 package.AddTest('android.tests.sigtest.SignatureTest.signatureTest')
129 description = open(os.path.join(self.test_repository, 'SignatureTest.xml'), 'w')
130 package.WriteDescription(description)
131 description.close()
132
133 def GenerateReferenceAppDescription(self):
134 """Generate the test description for the reference app tests."""
135 self.__LogGenerateDescription('android.apidemos.cts')
136 package = tools.TestPackage('ApiDemosReferenceTest', 'android.apidemos.cts')
Brian Muramatsu94b366e2010-04-06 15:41:41 -0700137 package.AddAttribute('appNameSpace', 'android.apidemos.cts')
Phil Dubach0d6ef062009-08-12 18:13:16 -0700138 package.AddAttribute('packageToTest', 'com.example.android.apis')
139 package.AddAttribute('apkToTestName', 'ApiDemos')
140 package.AddAttribute('runner', 'android.test.InstrumentationTestRunner')
141 package.AddAttribute('referenceAppTest', 'true')
142 package.AddTest('android.apidemos.cts.ApiDemosTest.testNumberOfItemsInListView')
143 description = open(os.path.join(self.test_repository, 'ApiDemosReferenceTest.xml'), 'w')
144 package.WriteDescription(description)
145 description.close()
146
Phil Dubach60680482009-08-19 10:13:23 -0700147 def GenerateAppSecurityDescription(self):
148 """Generate the test description for the application security tests."""
149 test_root = 'cts/tests/appsecurity-tests'
150 makefile_name = os.path.join(test_root, 'Android.mk')
151 makefile_vars = GetMakeFileVars(makefile_name)
152 name = makefile_vars['LOCAL_MODULE']
153 package_name = 'android.tests.appsecurity'
154 self.__LogGenerateDescription(package_name)
155 temp_desc = os.path.join(self.temp_dir, 'description.xml')
156 self.RunDescriptionGeneratorDoclet(os.path.join(test_root, 'src'), temp_desc)
157 doc = dom.parse(temp_desc)
158 test_description = doc.getElementsByTagName('TestPackage')[0]
159 test_description.setAttribute('name', package_name)
160 test_description.setAttribute('appPackageName', package_name)
161 test_description.setAttribute('hostSideOnly', 'true')
162 test_description.setAttribute('jarPath', name + '.jar')
163 description = open(os.path.join(self.test_repository, package_name + '.xml'), 'w')
164 doc.writexml(description, addindent=' ', encoding='UTF-8')
165 description.close()
166
Phil Dubach0d6ef062009-08-12 18:13:16 -0700167 @staticmethod
168 def RelPath(path, start=os.getcwd()):
169 """Get a relative version of a path.
170
171 This is equivalent to os.path.relpath, which is only available since Python 2.6.
172
173 Args:
174 path: The path to transform.
175 start: The base path. Defaults to the current working directory.
176
177 Returns:
178 A transformed path that is relative to start.
179 """
180 path_dirs = os.path.abspath(path).split(os.path.sep)
181 start_dirs = os.path.abspath(start).split(os.path.sep)
182
183 num_common = len(os.path.commonprefix([start_dirs, path_dirs]))
184
185 result_dirs = ['..'] * (len(start_dirs) - num_common) + path_dirs[num_common:]
186 if result_dirs:
187 return os.path.join(*result_dirs)
188 return start
189
190 def GenerateTestDescriptions(self):
191 """Generate test descriptions for all packages."""
192 # individually generate descriptions not following conventions
193 self.GenerateSignatureCheckDescription()
194 self.GenerateReferenceAppDescription()
Phil Dubach60680482009-08-19 10:13:23 -0700195 self.GenerateAppSecurityDescription()
Phil Dubach0d6ef062009-08-12 18:13:16 -0700196
197 # generate test descriptions for android tests
198 android_packages = GetSubDirectories(self.test_root)
199 for package in android_packages:
200 app_package_name = 'android.' + package
Phil Dubach98b23342009-08-24 17:58:05 -0700201 package_root = os.path.join(self.test_root, package)
202
203 makefile_name = os.path.join(package_root, 'Android.mk')
204 if not os.path.exists(makefile_name):
205 print 'Skipping directory "%s" due to missing Android.mk' % package_root
206 continue
207 makefile_vars = GetMakeFileVars(makefile_name)
208
209 manifest_name = os.path.join(package_root, 'AndroidManifest.xml')
210 if not os.path.exists(manifest_name):
211 print 'Skipping directory "%s" due to missing AndroidManifest.xml' % package_root
212 continue
213 manifest = tools.XmlFile(manifest_name)
214
Phil Dubach0d6ef062009-08-12 18:13:16 -0700215 self.__LogGenerateDescription(app_package_name)
216
217 # Run the description generator doclet to get the test package structure
218 # TODO: The Doclet does not currently add all required attributes. Instead of rewriting
219 # the document below, additional attributes should be passed to the Doclet as arguments.
220 temp_desc = os.path.join(self.temp_dir, 'description.xml')
Phil Dubach98b23342009-08-24 17:58:05 -0700221 self.RunDescriptionGeneratorDoclet(package_root, temp_desc)
Phil Dubach0d6ef062009-08-12 18:13:16 -0700222
223 # obtain missing attribute values from the makefile and manifest
224 package_name = makefile_vars['LOCAL_PACKAGE_NAME']
225 runner = manifest.GetAndroidAttr('instrumentation', 'name')
226 target_package = manifest.GetAndroidAttr('instrumentation', 'targetPackage')
227 target_binary_name = makefile_vars.get('LOCAL_INSTRUMENTATION_FOR')
228
229 # add them to the document
230 doc = dom.parse(temp_desc)
231 test_description = doc.getElementsByTagName('TestPackage')[0]
232 test_description.setAttribute('name', package_name)
233 test_description.setAttribute('runner', runner)
234 test_package = manifest.GetAttr('manifest', 'package')
235 test_description.setAttribute('appNameSpace', test_package)
236 test_description.setAttribute('appPackageName', app_package_name)
237 if not test_package == target_package:
238 test_description.setAttribute('targetNameSpace', target_package)
239 test_description.setAttribute('targetBinaryName', target_binary_name)
240 description = open(os.path.join(self.test_repository, package_name + '.xml'), 'w')
241 doc.writexml(description, addindent=' ', encoding='UTF-8')
242 description.close()
243
244 def __WritePlan(self, plan, plan_name):
245 print 'Generating test plan %s' % plan_name
246 plan.Write(os.path.join(self.plan_repository, plan_name + '.xml'))
247
248 def GenerateTestPlans(self):
249 """Generate default test plans."""
250 # TODO: Instead of hard-coding the plans here, use a configuration file,
251 # such as test_defs.xml
252 packages = []
Phil Dubach8dcedfe2009-08-20 16:10:39 -0700253 descriptions = sorted(glob.glob(os.path.join(self.test_repository, '*.xml')))
Phil Dubach0d6ef062009-08-12 18:13:16 -0700254 for description in descriptions:
255 doc = tools.XmlFile(description)
256 packages.append(doc.GetAttr('TestPackage', 'appPackageName'))
257
258 plan = tools.TestPlan(packages)
259 plan.Exclude('android\.performance.*')
260 self.__WritePlan(plan, 'CTS')
261 plan.Exclude(r'android\.tests\.sigtest')
262 plan.Exclude(r'android\.core.*')
263 self.__WritePlan(plan, 'Android')
264
265 plan = tools.TestPlan(packages)
266 plan.Include(r'android\.core\.tests.*')
267 self.__WritePlan(plan, 'Java')
268
269 plan = tools.TestPlan(packages)
270 plan.Include(r'android\.core\.vm-tests')
271 self.__WritePlan(plan, 'VM')
272
273 plan = tools.TestPlan(packages)
274 plan.Include(r'android\.tests\.sigtest')
275 self.__WritePlan(plan, 'Signature')
276
277 plan = tools.TestPlan(packages)
278 plan.Include(r'android\.apidemos\.cts')
279 self.__WritePlan(plan, 'RefApp')
280
281 plan = tools.TestPlan(packages)
282 plan.Include(r'android\.performance.*')
283 self.__WritePlan(plan, 'Performance')
284
Phil Dubach60680482009-08-19 10:13:23 -0700285 plan = tools.TestPlan(packages)
286 plan.Include(r'android\.tests\.appsecurity')
287 self.__WritePlan(plan, 'AppSecurity')
288
Phil Dubach0d6ef062009-08-12 18:13:16 -0700289
290if __name__ == '__main__':
291 builder = CtsBuilder(sys.argv)
292 builder.GenerateTestDescriptions()
293 builder.GenerateTestPlans()