blob: b3e9183d780450f8b7424e49945ea41221243410 [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
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
Phil Dubach60680482009-08-19 10:13:23 -0700144 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
Phil Dubach0d6ef062009-08-12 18:13:16 -0700164 @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()
Phil Dubach60680482009-08-19 10:13:23 -0700192 self.GenerateAppSecurityDescription()
Phil Dubach0d6ef062009-08-12 18:13:16 -0700193
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
Phil Dubach98b23342009-08-24 17:58:05 -0700198 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
Phil Dubach0d6ef062009-08-12 18:13:16 -0700212 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')
Phil Dubach98b23342009-08-24 17:58:05 -0700218 self.RunDescriptionGeneratorDoclet(package_root, temp_desc)
Phil Dubach0d6ef062009-08-12 18:13:16 -0700219
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 = []
Phil Dubach8dcedfe2009-08-20 16:10:39 -0700250 descriptions = sorted(glob.glob(os.path.join(self.test_repository, '*.xml')))
Phil Dubach0d6ef062009-08-12 18:13:16 -0700251 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
Phil Dubach60680482009-08-19 10:13:23 -0700282 plan = tools.TestPlan(packages)
283 plan.Include(r'android\.tests\.appsecurity')
284 self.__WritePlan(plan, 'AppSecurity')
285
Phil Dubach0d6ef062009-08-12 18:13:16 -0700286
287if __name__ == '__main__':
288 builder = CtsBuilder(sys.argv)
289 builder.GenerateTestDescriptions()
290 builder.GenerateTestPlans()