1# Copyright (C) 2018 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# 15 16"""App Engine local test runner. 17 18This program handles properly importing the App Engine SDK so that test modules 19can use google.appengine.* APIs and the Google App Engine testbed. 20 21Example invocation: 22 23 $ python testrunner.py [--sdk-path ~/google-cloud-sdk] 24""" 25 26import argparse 27import os 28import subprocess 29import sys 30import unittest 31 32 33def ExecuteOneShellCommand(cmd): 34 """Executes one shell command and returns (stdout, stderr, exit_code). 35 36 Args: 37 cmd: string, a shell command. 38 39 Returns: 40 tuple(string, string, int), containing stdout, stderr, exit_code of 41 the shell command. 42 """ 43 p = subprocess.Popen( 44 str(cmd), shell=True, 45 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 46 stdout, stderr = p.communicate() 47 return (stdout, stderr, p.returncode) 48 49 50def fixup_paths(path): 51 """Adds GAE SDK path to system path and appends it to the google path 52 if that already exists.""" 53 # Not all Google packages are inside namespace packages, which means 54 # there might be another non-namespace package named `google` already on 55 # the path and simply appending the App Engine SDK to the path will not 56 # work since the other package will get discovered and used first. 57 # This emulates namespace packages by first searching if a `google` package 58 # exists by importing it, and if so appending to its module search path. 59 try: 60 import google 61 google.__path__.append("{0}/google".format(path)) 62 except ImportError: 63 pass 64 65 sys.path.insert(0, path) 66 67 68def main(sdk_path, test_path, test_pattern): 69 70 if not sdk_path: 71 # Get sdk path by running gcloud command. 72 stdout, stderr, _ = ExecuteOneShellCommand( 73 "gcloud info --format='value(installation.sdk_root)'") 74 75 if stderr: 76 print("Cannot find google cloud sdk path.") 77 return 1 78 sdk_path = str.strip(stdout) 79 80 # If the SDK path points to a Google Cloud SDK installation 81 # then we should alter it to point to the GAE platform location. 82 if os.path.exists(os.path.join(sdk_path, 'platform/google_appengine')): 83 sdk_path = os.path.join(sdk_path, 'platform/google_appengine') 84 85 # Make sure google.appengine.* modules are importable. 86 fixup_paths(sdk_path) 87 88 # Make sure all bundled third-party packages are available. 89 import dev_appserver 90 dev_appserver.fix_sys_path() 91 92 # Loading appengine_config from the current project ensures that any 93 # changes to configuration there are available to all tests (e.g. 94 # sys.path modifications, namespaces, etc.) 95 try: 96 import appengine_config 97 (appengine_config) 98 except ImportError: 99 print('Note: unable to import appengine_config.') 100 101 # Discover and run tests. 102 suite = unittest.loader.TestLoader().discover(test_path, test_pattern) 103 print('Suite', suite) 104 return unittest.TextTestRunner(verbosity=2).run(suite) 105 106 107if __name__ == '__main__': 108 parser = argparse.ArgumentParser( 109 description=__doc__, 110 formatter_class=argparse.RawDescriptionHelpFormatter) 111 parser.add_argument( 112 '--sdk_path', 113 help='The path to the Google App Engine SDK or the Google Cloud SDK.', 114 default=None) 115 parser.add_argument( 116 '--test-path', 117 help='The path to look for tests, defaults to the current directory.', 118 default=os.getcwd()) 119 parser.add_argument( 120 '--test-pattern', 121 help='The file pattern for test modules, defaults to *_test.py.', 122 default='*_test.py') 123 124 args = parser.parse_args() 125 126 result = main(args.sdk_path, args.test_path, args.test_pattern) 127 128 if not result.wasSuccessful(): 129 sys.exit(1) 130