blob: a8a9c8b755d7bf6b8641c7eb131543b89489bbd6 [file] [log] [blame]
Phil Dubach0d6ef062009-08-12 18:13:16 -07001#!/usr/bin/python2.4
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 'dalvik/libcore/dalvik/src/main/java', # test annotations
99 'cts/tests/src', # cts test stubs
100 source_root # the source for this package
101 ]
102 source_path = [os.path.join(self.android_root, x) for x in source_path]
103 cmd = ('javadoc -o %s -J-Xmx512m -quiet -doclet DescriptionGenerator -docletpath %s'
104 ' -sourcepath %s ') % (output_file, self.doclet_path, ':'.join(source_path))
105 sources = []
106
107 def AddFile(sources, folder, names):
108 """Find *.java."""
109 sources.extend([os.path.join(folder, name) for name in names if name.endswith('.java')])
110
111 os.path.walk(os.path.join(self.android_root, source_root), AddFile, sources)
112 cmd += ' '.join(sources)
113 proc = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
114 # read and discard any output
115 proc.communicate()
116 # wait for process to terminate and return exit value
117 return proc.wait()
118
119 def GenerateSignatureCheckDescription(self):
120 """Generate the test description for the signature check."""
121 self.__LogGenerateDescription('android.tests.sigtest')
122 package = tools.TestPackage('SignatureTest', 'android.tests.sigtest')
123 package.AddAttribute('signatureCheck', 'true')
124 package.AddAttribute('runner', '.InstrumentationRunner')
125 package.AddTest('android.tests.sigtest.SignatureTest.signatureTest')
126 description = open(os.path.join(self.test_repository, 'SignatureTest.xml'), 'w')
127 package.WriteDescription(description)
128 description.close()
129
130 def GenerateReferenceAppDescription(self):
131 """Generate the test description for the reference app tests."""
132 self.__LogGenerateDescription('android.apidemos.cts')
133 package = tools.TestPackage('ApiDemosReferenceTest', 'android.apidemos.cts')
134 package.AddAttribute('packageToTest', 'com.example.android.apis')
135 package.AddAttribute('apkToTestName', 'ApiDemos')
136 package.AddAttribute('runner', 'android.test.InstrumentationTestRunner')
137 package.AddAttribute('referenceAppTest', 'true')
138 package.AddTest('android.apidemos.cts.ApiDemosTest.testNumberOfItemsInListView')
139 description = open(os.path.join(self.test_repository, 'ApiDemosReferenceTest.xml'), 'w')
140 package.WriteDescription(description)
141 description.close()
142
143 @staticmethod
144 def RelPath(path, start=os.getcwd()):
145 """Get a relative version of a path.
146
147 This is equivalent to os.path.relpath, which is only available since Python 2.6.
148
149 Args:
150 path: The path to transform.
151 start: The base path. Defaults to the current working directory.
152
153 Returns:
154 A transformed path that is relative to start.
155 """
156 path_dirs = os.path.abspath(path).split(os.path.sep)
157 start_dirs = os.path.abspath(start).split(os.path.sep)
158
159 num_common = len(os.path.commonprefix([start_dirs, path_dirs]))
160
161 result_dirs = ['..'] * (len(start_dirs) - num_common) + path_dirs[num_common:]
162 if result_dirs:
163 return os.path.join(*result_dirs)
164 return start
165
166 def GenerateTestDescriptions(self):
167 """Generate test descriptions for all packages."""
168 # individually generate descriptions not following conventions
169 self.GenerateSignatureCheckDescription()
170 self.GenerateReferenceAppDescription()
171
172 # generate test descriptions for android tests
173 android_packages = GetSubDirectories(self.test_root)
174 for package in android_packages:
175 app_package_name = 'android.' + package
176 self.__LogGenerateDescription(app_package_name)
177
178 # Run the description generator doclet to get the test package structure
179 # TODO: The Doclet does not currently add all required attributes. Instead of rewriting
180 # the document below, additional attributes should be passed to the Doclet as arguments.
181 temp_desc = os.path.join(self.temp_dir, 'description.xml')
182 self.RunDescriptionGeneratorDoclet(os.path.join(self.test_root, package), temp_desc)
183
184 makefile_name = os.path.join(self.test_root, package, 'Android.mk')
185 makefile_vars = GetMakeFileVars(makefile_name)
186 manifest = tools.XmlFile(os.path.join(self.test_root, package, 'AndroidManifest.xml'))
187
188 # obtain missing attribute values from the makefile and manifest
189 package_name = makefile_vars['LOCAL_PACKAGE_NAME']
190 runner = manifest.GetAndroidAttr('instrumentation', 'name')
191 target_package = manifest.GetAndroidAttr('instrumentation', 'targetPackage')
192 target_binary_name = makefile_vars.get('LOCAL_INSTRUMENTATION_FOR')
193
194 # add them to the document
195 doc = dom.parse(temp_desc)
196 test_description = doc.getElementsByTagName('TestPackage')[0]
197 test_description.setAttribute('name', package_name)
198 test_description.setAttribute('runner', runner)
199 test_package = manifest.GetAttr('manifest', 'package')
200 test_description.setAttribute('appNameSpace', test_package)
201 test_description.setAttribute('appPackageName', app_package_name)
202 if not test_package == target_package:
203 test_description.setAttribute('targetNameSpace', target_package)
204 test_description.setAttribute('targetBinaryName', target_binary_name)
205 description = open(os.path.join(self.test_repository, package_name + '.xml'), 'w')
206 doc.writexml(description, addindent=' ', encoding='UTF-8')
207 description.close()
208
209 def __WritePlan(self, plan, plan_name):
210 print 'Generating test plan %s' % plan_name
211 plan.Write(os.path.join(self.plan_repository, plan_name + '.xml'))
212
213 def GenerateTestPlans(self):
214 """Generate default test plans."""
215 # TODO: Instead of hard-coding the plans here, use a configuration file,
216 # such as test_defs.xml
217 packages = []
Phil Dubach8dcedfe2009-08-20 16:10:39 -0700218 descriptions = sorted(glob.glob(os.path.join(self.test_repository, '*.xml')))
Phil Dubach0d6ef062009-08-12 18:13:16 -0700219 for description in descriptions:
220 doc = tools.XmlFile(description)
221 packages.append(doc.GetAttr('TestPackage', 'appPackageName'))
222
223 plan = tools.TestPlan(packages)
224 plan.Exclude('android\.performance.*')
225 self.__WritePlan(plan, 'CTS')
226 plan.Exclude(r'android\.tests\.sigtest')
227 plan.Exclude(r'android\.core.*')
228 self.__WritePlan(plan, 'Android')
229
230 plan = tools.TestPlan(packages)
231 plan.Include(r'android\.core\.tests.*')
232 self.__WritePlan(plan, 'Java')
233
234 plan = tools.TestPlan(packages)
235 plan.Include(r'android\.core\.vm-tests')
236 self.__WritePlan(plan, 'VM')
237
238 plan = tools.TestPlan(packages)
239 plan.Include(r'android\.tests\.sigtest')
240 self.__WritePlan(plan, 'Signature')
241
242 plan = tools.TestPlan(packages)
243 plan.Include(r'android\.apidemos\.cts')
244 self.__WritePlan(plan, 'RefApp')
245
246 plan = tools.TestPlan(packages)
247 plan.Include(r'android\.performance.*')
248 self.__WritePlan(plan, 'Performance')
249
250
251if __name__ == '__main__':
252 builder = CtsBuilder(sys.argv)
253 builder.GenerateTestDescriptions()
254 builder.GenerateTestPlans()