blob: b1a67001ac24a2d6800ede1ea97a09c72877db3a [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')
Brian Muramatsuf176eaf2010-07-27 14:27:01 -0700124 package.AddAttribute('appNameSpace', 'android.tests.sigtest')
Phil Dubach0d6ef062009-08-12 18:13:16 -0700125 package.AddAttribute('signatureCheck', 'true')
126 package.AddAttribute('runner', '.InstrumentationRunner')
127 package.AddTest('android.tests.sigtest.SignatureTest.signatureTest')
128 description = open(os.path.join(self.test_repository, 'SignatureTest.xml'), 'w')
129 package.WriteDescription(description)
130 description.close()
131
132 def GenerateReferenceAppDescription(self):
133 """Generate the test description for the reference app tests."""
134 self.__LogGenerateDescription('android.apidemos.cts')
135 package = tools.TestPackage('ApiDemosReferenceTest', 'android.apidemos.cts')
Brian Muramatsuf176eaf2010-07-27 14:27:01 -0700136 package.AddAttribute('appNameSpace', 'android.apidemos.cts')
Phil Dubach0d6ef062009-08-12 18:13:16 -0700137 package.AddAttribute('packageToTest', 'com.example.android.apis')
138 package.AddAttribute('apkToTestName', 'ApiDemos')
139 package.AddAttribute('runner', 'android.test.InstrumentationTestRunner')
140 package.AddAttribute('referenceAppTest', 'true')
141 package.AddTest('android.apidemos.cts.ApiDemosTest.testNumberOfItemsInListView')
142 description = open(os.path.join(self.test_repository, 'ApiDemosReferenceTest.xml'), 'w')
143 package.WriteDescription(description)
144 description.close()
145
Phil Dubach60680482009-08-19 10:13:23 -0700146 def GenerateAppSecurityDescription(self):
147 """Generate the test description for the application security tests."""
148 test_root = 'cts/tests/appsecurity-tests'
149 makefile_name = os.path.join(test_root, 'Android.mk')
150 makefile_vars = GetMakeFileVars(makefile_name)
151 name = makefile_vars['LOCAL_MODULE']
152 package_name = 'android.tests.appsecurity'
153 self.__LogGenerateDescription(package_name)
154 temp_desc = os.path.join(self.temp_dir, 'description.xml')
155 self.RunDescriptionGeneratorDoclet(os.path.join(test_root, 'src'), temp_desc)
156 doc = dom.parse(temp_desc)
157 test_description = doc.getElementsByTagName('TestPackage')[0]
158 test_description.setAttribute('name', package_name)
159 test_description.setAttribute('appPackageName', package_name)
160 test_description.setAttribute('hostSideOnly', 'true')
161 test_description.setAttribute('jarPath', name + '.jar')
162 description = open(os.path.join(self.test_repository, package_name + '.xml'), 'w')
163 doc.writexml(description, addindent=' ', encoding='UTF-8')
164 description.close()
165
Phil Dubach0d6ef062009-08-12 18:13:16 -0700166 @staticmethod
167 def RelPath(path, start=os.getcwd()):
168 """Get a relative version of a path.
169
170 This is equivalent to os.path.relpath, which is only available since Python 2.6.
171
172 Args:
173 path: The path to transform.
174 start: The base path. Defaults to the current working directory.
175
176 Returns:
177 A transformed path that is relative to start.
178 """
179 path_dirs = os.path.abspath(path).split(os.path.sep)
180 start_dirs = os.path.abspath(start).split(os.path.sep)
181
182 num_common = len(os.path.commonprefix([start_dirs, path_dirs]))
183
184 result_dirs = ['..'] * (len(start_dirs) - num_common) + path_dirs[num_common:]
185 if result_dirs:
186 return os.path.join(*result_dirs)
187 return start
188
189 def GenerateTestDescriptions(self):
190 """Generate test descriptions for all packages."""
191 # individually generate descriptions not following conventions
192 self.GenerateSignatureCheckDescription()
193 self.GenerateReferenceAppDescription()
Phil Dubach60680482009-08-19 10:13:23 -0700194 self.GenerateAppSecurityDescription()
Phil Dubach0d6ef062009-08-12 18:13:16 -0700195
196 # generate test descriptions for android tests
197 android_packages = GetSubDirectories(self.test_root)
198 for package in android_packages:
199 app_package_name = 'android.' + package
Phil Dubach98b23342009-08-24 17:58:05 -0700200 package_root = os.path.join(self.test_root, package)
201
202 makefile_name = os.path.join(package_root, 'Android.mk')
203 if not os.path.exists(makefile_name):
204 print 'Skipping directory "%s" due to missing Android.mk' % package_root
205 continue
206 makefile_vars = GetMakeFileVars(makefile_name)
207
208 manifest_name = os.path.join(package_root, 'AndroidManifest.xml')
209 if not os.path.exists(manifest_name):
210 print 'Skipping directory "%s" due to missing AndroidManifest.xml' % package_root
211 continue
212 manifest = tools.XmlFile(manifest_name)
213
Phil Dubach0d6ef062009-08-12 18:13:16 -0700214 self.__LogGenerateDescription(app_package_name)
215
216 # Run the description generator doclet to get the test package structure
217 # TODO: The Doclet does not currently add all required attributes. Instead of rewriting
218 # the document below, additional attributes should be passed to the Doclet as arguments.
219 temp_desc = os.path.join(self.temp_dir, 'description.xml')
Phil Dubach98b23342009-08-24 17:58:05 -0700220 self.RunDescriptionGeneratorDoclet(package_root, temp_desc)
Phil Dubach0d6ef062009-08-12 18:13:16 -0700221
222 # obtain missing attribute values from the makefile and manifest
223 package_name = makefile_vars['LOCAL_PACKAGE_NAME']
224 runner = manifest.GetAndroidAttr('instrumentation', 'name')
225 target_package = manifest.GetAndroidAttr('instrumentation', 'targetPackage')
226 target_binary_name = makefile_vars.get('LOCAL_INSTRUMENTATION_FOR')
227
228 # add them to the document
229 doc = dom.parse(temp_desc)
230 test_description = doc.getElementsByTagName('TestPackage')[0]
231 test_description.setAttribute('name', package_name)
232 test_description.setAttribute('runner', runner)
233 test_package = manifest.GetAttr('manifest', 'package')
234 test_description.setAttribute('appNameSpace', test_package)
235 test_description.setAttribute('appPackageName', app_package_name)
236 if not test_package == target_package:
237 test_description.setAttribute('targetNameSpace', target_package)
238 test_description.setAttribute('targetBinaryName', target_binary_name)
239 description = open(os.path.join(self.test_repository, package_name + '.xml'), 'w')
240 doc.writexml(description, addindent=' ', encoding='UTF-8')
241 description.close()
242
243 def __WritePlan(self, plan, plan_name):
244 print 'Generating test plan %s' % plan_name
245 plan.Write(os.path.join(self.plan_repository, plan_name + '.xml'))
246
247 def GenerateTestPlans(self):
248 """Generate default test plans."""
249 # TODO: Instead of hard-coding the plans here, use a configuration file,
250 # such as test_defs.xml
251 packages = []
Phil Dubach8dcedfe2009-08-20 16:10:39 -0700252 descriptions = sorted(glob.glob(os.path.join(self.test_repository, '*.xml')))
Phil Dubach0d6ef062009-08-12 18:13:16 -0700253 for description in descriptions:
254 doc = tools.XmlFile(description)
255 packages.append(doc.GetAttr('TestPackage', 'appPackageName'))
256
257 plan = tools.TestPlan(packages)
258 plan.Exclude('android\.performance.*')
259 self.__WritePlan(plan, 'CTS')
260 plan.Exclude(r'android\.tests\.sigtest')
261 plan.Exclude(r'android\.core.*')
262 self.__WritePlan(plan, 'Android')
263
264 plan = tools.TestPlan(packages)
265 plan.Include(r'android\.core\.tests.*')
266 self.__WritePlan(plan, 'Java')
267
268 plan = tools.TestPlan(packages)
269 plan.Include(r'android\.core\.vm-tests')
270 self.__WritePlan(plan, 'VM')
271
272 plan = tools.TestPlan(packages)
273 plan.Include(r'android\.tests\.sigtest')
274 self.__WritePlan(plan, 'Signature')
275
276 plan = tools.TestPlan(packages)
277 plan.Include(r'android\.apidemos\.cts')
278 self.__WritePlan(plan, 'RefApp')
279
280 plan = tools.TestPlan(packages)
281 plan.Include(r'android\.performance.*')
282 self.__WritePlan(plan, 'Performance')
283
Phil Dubach60680482009-08-19 10:13:23 -0700284 plan = tools.TestPlan(packages)
285 plan.Include(r'android\.tests\.appsecurity')
286 self.__WritePlan(plan, 'AppSecurity')
287
Phil Dubach0d6ef062009-08-12 18:13:16 -0700288
289if __name__ == '__main__':
290 builder = CtsBuilder(sys.argv)
291 builder.GenerateTestDescriptions()
292 builder.GenerateTestPlans()