diff options
author | Jack Wang <jackwang@google.com> | 2009-06-29 18:47:03 -0700 |
---|---|---|
committer | Jack Wang <jackwang@google.com> | 2009-06-30 18:39:33 -0700 |
commit | 2abd80af5aec583a8095d11aded4705a30ef54b5 (patch) | |
tree | 54a4b00fb17b482502c368ad36ae80b8600f4308 | |
parent | 6683fa352a7ed5d2c02c227da1c7268640c19aa4 (diff) | |
download | android_development-2abd80af5aec583a8095d11aded4705a30ef54b5.tar.gz android_development-2abd80af5aec583a8095d11aded4705a30ef54b5.tar.bz2 android_development-2abd80af5aec583a8095d11aded4705a30ef54b5.zip |
Fix bug 1844502: Add create_test.py to generate Android.mk and AndroidManifest.xml for application tests.
-rw-r--r-- | testrunner/android_manifest.py | 60 | ||||
-rw-r--r-- | testrunner/android_mk.py | 96 | ||||
-rwxr-xr-x | testrunner/create_test.py | 245 |
3 files changed, 401 insertions, 0 deletions
diff --git a/testrunner/android_manifest.py b/testrunner/android_manifest.py new file mode 100644 index 000000000..7ede96c58 --- /dev/null +++ b/testrunner/android_manifest.py @@ -0,0 +1,60 @@ +#!/usr/bin/python2.4 +# +# +# Copyright 2009, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""In memory representation of AndroidManifest.xml file. + +Specification of AndroidManifest.xml can be found at +http://developer.android.com/guide/topics/manifest/manifest-intro.html +""" + +# python imports +import xml.dom.minidom +import xml.parsers + + +class AndroidManifest(object): + """In memory representation of AndroidManifest.xml file.""" + + FILENAME = "AndroidManifest.xml" + + def __init__(self, app_path=None): + if app_path: + self.ParseManifest(app_path) + + def GetPackageName(self): + """Retrieve package name defined at <manifest package="...">. + + Returns: + Package name if defined, otherwise None + """ + manifests = self._dom.getElementsByTagName("manifest") + if not manifests or not manifests[0].getAttribute("package"): + return None + return manifests[0].getAttribute("package") + + def ParseManifest(self, app_path): + """Parse AndroidManifest.xml at the specified path. + + Args: + app_path: path to folder containing AndroidManifest.xml + Raises: + IOError: AndroidManifest.xml cannot be found at given path, or cannot be + opened for reading + """ + self.app_path = app_path.rstrip("/") + self.manifest_path = "%s/%s" % (self.app_path, self.FILENAME) + self._dom = xml.dom.minidom.parse(self.manifest_path) diff --git a/testrunner/android_mk.py b/testrunner/android_mk.py new file mode 100644 index 000000000..663aa5c14 --- /dev/null +++ b/testrunner/android_mk.py @@ -0,0 +1,96 @@ +#!/usr/bin/python2.4 +# +# +# Copyright 2009, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""In memory representation of Android.mk file. + +Specifications for Android.mk can be found at +development/ndk/docs/ANDROID-MK.txt +""" + +import re +from sets import Set + + +class AndroidMK(object): + """In memory representation of Android.mk file.""" + + _RE_INCLUDE = re.compile(r'include\s+\$\((.+)\)') + _VAR_DELIMITER = ":=" + FILENAME = "Android.mk" + CERTIFICATE = "LOCAL_CERTIFICATE" + PACKAGE_NAME = "LOCAL_PACKAGE_NAME" + + def __init__(self, app_path=None): + self._includes = Set() # variables included in makefile + self._variables = {} # variables defined in makefile + + if app_path: + self.ParseMK(app_path) + + def _ProcessMKLine(self, line): + """Add a variable definition or include. + + Ignores unrecognized lines. + + Args: + line: line of text from makefile + """ + m = self._RE_INCLUDE.match(line) + if m: + self._includes.add(m.group(1)) + else: + parts = line.split(self._VAR_DELIMITER) + if len(parts) > 1: + self._variables[parts[0].strip()] = parts[1].strip() + + def GetVariable(self, identifier): + """Retrieve makefile variable. + + Args: + identifier: name of variable to retrieve + Returns: + value of specified identifier, None if identifier not found in makefile + """ + # use dict.get(x) rather than dict[x] to avoid KeyError exception, + # so None is returned if identifier not found + return self._variables.get(identifier, None) + + def HasInclude(self, identifier): + """Check variable is included in makefile. + + Args: + identifer: name of variable to check + Returns: + True if identifer is included in makefile, otherwise False + """ + return identifier in self._includes + + def ParseMK(self, app_path): + """Parse Android.mk at the specified path. + + Args: + app_path: path to folder containing Android.mk + Raises: + IOError: Android.mk cannot be found at given path, or cannot be opened + for reading + """ + self.app_path = app_path.rstrip("/") + self.mk_path = "%s/%s" % (self.app_path, self.FILENAME) + mk = open(self.mk_path) + for line in mk: + self._ProcessMKLine(line) + mk.close() diff --git a/testrunner/create_test.py b/testrunner/create_test.py new file mode 100755 index 000000000..2fc3a3cff --- /dev/null +++ b/testrunner/create_test.py @@ -0,0 +1,245 @@ +#!/usr/bin/python2.4 +# +# +# Copyright 2009, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utility to create Android project files for tests.""" + +# python imports +import datetime +import optparse +import os +import string +import sys + +# local imports +import android_mk +import android_manifest + + +class TestsConsts(object): + """Constants for test Android.mk and AndroidManifest.xml creation.""" + + MK_BUILD_INCLUDE = "call all-makefiles-under,$(LOCAL_PATH)" + MK_BUILD_STRING = "\ninclude $(%s)\n" % MK_BUILD_INCLUDE + TEST_MANIFEST_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) $YEAR The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="$PACKAGE_NAME.tests"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="$PACKAGE_NAME" + android:label="Tests for $MODULE_NAME"> + </instrumentation> +</manifest> +""" + TEST_MK_TEMPLATE = """LOCAL_PATH := $$(call my-dir) +include $$(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_JAVA_LIBRARIES := android.test.runner + +LOCAL_SRC_FILES := $$(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := ${MODULE_NAME}Tests${CERTIFICATE} + +LOCAL_INSTRUMENTATION_FOR := ${MODULE_NAME} + +LOCAL_SDK_VERSION := current + +include $$(BUILD_PACKAGE) +""" + TESTS_FOLDER = "tests" + + +def _GenerateTestManifest(manifest, module_name, mapping=None): + """Create and populate tests/AndroidManifest.xml with variable values from + Android.mk and AndroidManifest.xml. + + Does nothing if tests/AndroidManifest.xml already exists. + + Args: + manifest: AndroidManifest object for application manifest + module_name: module name used for labelling + mapping: optional user defined mapping of variable values, replaces values + extracted from AndroidManifest.xml + Raises: + IOError: tests/AndroidManifest.xml cannot be opened for writing + """ + # skip if file already exists + tests_path = "%s/%s" % (manifest.app_path, TestsConsts.TESTS_FOLDER) + tests_manifest_path = "%s/%s" % (tests_path, manifest.FILENAME) + if os.path.exists(tests_manifest_path): + _PrintMessage("%s already exists, not overwritten" % tests_manifest_path) + return + + if not mapping: + package_name = manifest.GetPackageName() + mapping = {"PACKAGE_NAME":package_name, "MODULE_NAME":module_name, + "YEAR":datetime.date.today().year} + output = string.Template(TestsConsts.TEST_MANIFEST_TEMPLATE).substitute(mapping) + + # create tests folder if not existent + if not os.path.exists(tests_path): + os.mkdir(tests_path) + + # write tests/AndroidManifest.xml + tests_manifest = open(tests_manifest_path, mode="w") + tests_manifest.write(output) + tests_manifest.close() + _PrintMessage("Created %s" % tests_manifest_path) + + +def _GenerateTestMK(mk, mapping=None): + """Create and populate tests/Android.mk with variable values from Android.mk. + + Does nothing if tests/Android.mk already exists. + + Args: + mk: AndroidMK object for application makefile + mapping: optional user defined mapping of variable values, replaces + values stored in mk + Raises: + IOError: tests/Android.mk cannot be opened for writing + """ + # skip if file already exists + tests_path = "%s/%s" % (mk.app_path, TestsConsts.TESTS_FOLDER) + tests_mk_path = "%s/%s" % (tests_path, mk.FILENAME) + if os.path.exists(tests_mk_path): + _PrintMessage("%s already exists, not overwritten" % tests_mk_path) + return + + # append test build if not existent in makefile + if not mk.HasInclude(TestsConsts.MK_BUILD_INCLUDE): + mk_path = "%s/%s" % (mk.app_path, mk.FILENAME) + mk_file = open(mk_path, mode="a") + mk_file.write(TestsConsts.MK_BUILD_STRING) + mk_file.close() + + # construct tests/Android.mk + # include certificate definition if existent in makefile + certificate = mk.GetVariable(mk.CERTIFICATE) + if certificate: + cert_definition = ("\n%s := %s" % (mk.CERTIFICATE, certificate)) + else: + cert_definition = "" + if not mapping: + module_name = mk.GetVariable(mk.PACKAGE_NAME) + mapping = {"MODULE_NAME":module_name, "CERTIFICATE":cert_definition} + output = string.Template(TestsConsts.TEST_MK_TEMPLATE).substitute(mapping) + + # create tests folder if not existent + if not os.path.exists(tests_path): + os.mkdir(tests_path) + + # write tests/Android.mk to disk + tests_mk = open(tests_mk_path, mode="w") + tests_mk.write(output) + tests_mk.close() + _PrintMessage("Created %s" % tests_mk_path) + + +def _ParseArgs(argv): + """Parse the command line arguments. + + Args: + argv: the list of command line arguments + Returns: + a tuple of options and individual command line arguments. + """ + parser = optparse.OptionParser(usage="%s <app_path>" % sys.argv[0]) + options, args = parser.parse_args(argv) + if len(args) < 1: + _PrintError("Error: Incorrect syntax") + parser.print_usage() + sys.exit() + return (options, args) + + +def _PrintMessage(msg): + print >> sys.stdout, msg + + +def _PrintError(msg): + print >> sys.stderr, msg + + +def _ValidateInputFiles(mk, manifest): + """Verify that required variables are defined in input files. + + Args: + mk: AndroidMK object for application makefile + manifest: AndroidManifest object for application manifest + Raises: + RuntimeError: mk does not define LOCAL_PACKAGE_NAME or + manifest does not define package variable + """ + module_name = mk.GetVariable(mk.PACKAGE_NAME) + if not module_name: + raise RuntimeError("Variable %s missing from %s" % + (mk.PACKAGE_NAME, mk.FILENAME)) + + package_name = manifest.GetPackageName() + if not package_name: + raise RuntimeError("Variable package missing from %s" % manifest.FILENAME) + + +def main(argv): + options, args = _ParseArgs(argv) + app_path = args[0]; + + if not os.path.exists(app_path): + _PrintError("Error: Application path %s not found" % app_path) + sys.exit() + + try: + mk = android_mk.AndroidMK(app_path=app_path) + manifest = android_manifest.AndroidManifest(app_path=app_path) + _ValidateInputFiles(mk, manifest) + + module_name = mk.GetVariable(mk.PACKAGE_NAME) + _GenerateTestMK(mk) + _GenerateTestManifest(manifest, module_name) + except Exception, e: + _PrintError("Error: %s" % e) + _PrintError("Error encountered, script aborted") + sys.exit() + + src_path = app_path + "/tests/src" + if not os.path.exists(src_path): + os.mkdir(src_path) + + +if __name__ == "__main__": + main(sys.argv[1:]) |