1# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2# SPDX-License-Identifier: Apache-2.0. 3# 4 5import re 6import os 7import argparse 8import subprocess 9import shutil 10import time 11import datetime 12import sys 13 14TestName = "AndroidSDKTesting" 15TestLowerName = TestName.lower() 16 17def ArgumentException( Exception ): 18 def __init__( self, argumentName, argumentValue ): 19 self.m_argumentName = argumentName 20 self.m_argumentValue = argumentValue 21 22 23def ParseArguments(): 24 parser = argparse.ArgumentParser(description="AWSNativeSDK Android Test Script") 25 parser.add_argument("--clean", action="store_true") 26 parser.add_argument("--emu", action="store_true") 27 parser.add_argument("--abi", action="store") 28 parser.add_argument("--avd", action="store") 29 parser.add_argument("--nobuild", action="store_true") 30 parser.add_argument("--noinstall", action="store_true") 31 parser.add_argument("--runtest", action="store") 32 parser.add_argument("--credentials", action="store") 33 parser.add_argument("--build", action="store") 34 parser.add_argument("--so", action="store_true") 35 parser.add_argument("--stl", action="store") 36 37 args = vars( parser.parse_args() ) 38 39 argMap = {} 40 argMap[ "clean" ] = args[ "clean" ] 41 argMap[ "abi" ] = args[ "abi" ] or "armeabi-v7a" 42 argMap[ "avd" ] = args[ "avd" ] 43 argMap[ "useExistingEmulator" ] = args[ "emu" ] 44 argMap[ "noBuild" ] = args[ "nobuild" ] 45 argMap[ "noInstall" ] = args[ "noinstall" ] 46 argMap[ "credentialsFile" ] = args[ "credentials" ] or "~/.aws/credentials" 47 argMap[ "buildType" ] = args[ "build" ] or "Release" 48 argMap[ "runTest" ] = args[ "runtest" ] 49 argMap[ "so" ] = args[ "so" ] 50 argMap[ "stl" ] = args[ "stl" ] or "libc++_shared" 51 52 return argMap 53 54 55def IsValidABI(abi): 56 return abi == "armeabi-v7a" 57 58 59def ShouldBuildClean(abi, buildDir): 60 if not os.path.exists( buildDir ): 61 return True 62 63 abiPattern = re.compile("ANDROID_ABI:STRING=\s*(?P<abi>\S+)") 64 for _, line in enumerate(open(buildDir + "/CMakeCache.txt")): 65 result = abiPattern.search(line) 66 if result != None: 67 return result.group("abi") != abi 68 69 return False 70 71 72def BuildAvdAbiSet(): 73 namePattern = re.compile("Name:\s*(?P<name>\S+)") 74 abiPattern = re.compile("ABI: default/(?P<abi>\S+)") 75 avdList = subprocess.check_output(["android", "list", "avds"]) 76 avdABIs = {} 77 currentName = None 78 79 for _, line in enumerate(avdList.splitlines()): 80 if not currentName: 81 nameResult = namePattern.search(line) 82 if nameResult != None: 83 currentName = nameResult.group("name") 84 else: 85 abiResult = abiPattern.search(line) 86 if abiResult != None: 87 avdABIs[currentName] = abiResult.group("abi") 88 currentName = None 89 90 return avdABIs 91 92 93def DoesAVDSupportABI(avdAbi, abi): 94 if avdAbi == "armeabi-v7a": 95 return abi == "armeabi-v7a" or abi == "armeabi" 96 else: 97 return abi == avdAbi 98 99 100def FindAVDForABI(abi, avdABIs): 101 for avdName in avdABIs: 102 if DoesAVDSupportABI(avdABIs[avdName], abi): 103 return avdName 104 105 return None 106 107 108def IsValidAVD(avd, abi, avdABIs): 109 return DoesAVDSupportABI(avdABIs[avd], abi) 110 111 112def GetTestList(buildSharedObjects): 113 if buildSharedObjects: 114 return [ 'core', 's3', 'dynamodb', 'cloudfront', 'cognitoidentity', 'identity', 'lambda', 'logging', 'redshift', 'sqs', 'transfer' ] 115 else: 116 return [ 'unified' ] 117 118 119def ValidateArguments(buildDir, avd, abi, clean, runTest, buildSharedObjects): 120 121 validTests = GetTestList( buildSharedObjects ) 122 if runTest not in validTests: 123 print( 'Invalid value for runtest option: ' + runTest ) 124 print( 'Valid values are: ' ) 125 print( ' ' + ", ".join( validTests ) ) 126 raise ArgumentException('runtest', runTest) 127 128 if not IsValidABI(abi): 129 print('Invalid argument value for abi: ', abi) 130 print(' Valid values are "armeabi-v7a"') 131 raise ArgumentException('abi', abi) 132 133 if not clean and ShouldBuildClean(abi, buildDir): 134 clean = True 135 136 avdABIs = BuildAvdAbiSet() 137 138 if not avd: 139 print('No virtual device specified (--avd), trying to find one in the existing avd set...') 140 avd = FindAVDForABI(abi, avdABIs) 141 142 if not IsValidAVD(avd, abi, avdABIs): 143 print('Invalid virtual device: ', avd) 144 print(' Use --avd to set the virtual device') 145 print(' Use "android lists avds" to see all usable virtual devices') 146 raise ArgumentException('avd', avd) 147 148 return (avd, abi, clean) 149 150 151def SetupJniDirectory(abi, clean): 152 path = os.path.join( TestName, "app", "src", "main", "jniLibs", abi ) 153 154 if clean and os.path.exists(path): 155 shutil.rmtree(path) 156 157 if os.path.exists( path ) == False: 158 os.makedirs( path ) 159 160 return path 161 162 163def CopyNativeLibraries(buildSharedObjects, jniDir, buildDir, abi, stl): 164 baseToolchainDir = os.path.join(buildDir, 'toolchains', 'android') 165 toolchainDirList = os.listdir(baseToolchainDir) # should only be one entry 166 toolchainDir = os.path.join(baseToolchainDir, toolchainDirList[0]) 167 168 platformLibDir = os.path.join(toolchainDir, "sysroot", "usr", "lib") 169 shutil.copy(os.path.join(platformLibDir, "liblog.so"), jniDir) 170 171 stdLibDir = os.path.join(toolchainDir, 'arm-linux-androideabi', 'lib') 172 if stl == 'libc++_shared': 173 shutil.copy(os.path.join(stdLibDir, "libc++_shared.so"), jniDir) 174 elif stl == 'gnustl_shared': 175 shutil.copy(os.path.join(stdLibDir, "armv7-a", "libgnustl_shared.so"), jniDir) # TODO: remove armv7-a hardcoded path 176 177 if buildSharedObjects: 178 179 soPattern = re.compile(".*\.so$") 180 181 for rootDir, dirNames, fileNames in os.walk( buildDir ): 182 for fileName in fileNames: 183 if soPattern.search(fileName): 184 libFileName = os.path.join(rootDir, fileName) 185 shutil.copy(libFileName, jniDir) 186 187 else: 188 unifiedTestsLibrary = os.path.join(buildDir, "android-unified-tests", "libandroid-unified-tests.so") 189 shutil.copy(unifiedTestsLibrary, jniDir) 190 191def RemoveTree(dir): 192 if os.path.exists( dir ): 193 shutil.rmtree( dir ) 194 195 196def BuildNative(abi, clean, buildDir, jniDir, installDir, buildType, buildSharedObjects, stl): 197 if clean: 198 RemoveTree(installDir) 199 RemoveTree(buildDir) 200 RemoveTree(jniDir) 201 for externalProjectDir in [ "openssl", "zlib", "curl" ]: 202 RemoveTree(externalProjectDir) 203 204 os.makedirs( jniDir ) 205 os.makedirs( buildDir ) 206 os.chdir( buildDir ) 207 208 if not buildSharedObjects: 209 link_type_line = "-DBUILD_SHARED_LIBS=OFF" 210 else: 211 link_type_line = "-DBUILD_SHARED_LIBS=ON" 212 213 subprocess.check_call( [ "cmake", 214 link_type_line, 215 "-DCUSTOM_MEMORY_MANAGEMENT=ON", 216 "-DTARGET_ARCH=ANDROID", 217 "-DANDROID_ABI=" + abi, 218 "-DANDROID_STL=" + stl, 219 "-DCMAKE_BUILD_TYPE=" + buildType, 220 "-DENABLE_UNITY_BUILD=ON", 221 '-DTEST_CERT_PATH="/data/data/aws.' + TestLowerName + '/certs"', 222 '-DBUILD_ONLY=dynamodb;sqs;s3;lambda;kinesis;cognito-identity;transfer;iam;identity-management;access-management;s3-encryption', 223 ".."] ) 224 else: 225 os.chdir( buildDir ) 226 227 if buildSharedObjects: 228 subprocess.check_call( [ "make", "-j12" ] ) 229 else: 230 subprocess.check_call( [ "make", "-j12", "android-unified-tests" ] ) 231 232 os.chdir( ".." ) 233 CopyNativeLibraries(buildSharedObjects, jniDir, buildDir, abi, stl) 234 235 236def BuildJava(clean): 237 os.chdir( TestName ) 238 if clean: 239 subprocess.check_call( [ "./gradlew", "clean" ] ) 240 subprocess.check_call( [ "./gradlew", "--refresh-dependencies" ] ) 241 242 subprocess.check_call( [ "./gradlew", "assembleDebug" ] ) 243 os.chdir( ".." ) 244 245 246def IsAnEmulatorRunning(): 247 emulatorPattern = re.compile("(?P<emu>emulator-\d+)") 248 emulatorList = subprocess.check_output(["adb", "devices"]) 249 250 for _, line in enumerate(emulatorList.splitlines()): 251 result = emulatorPattern.search(line) 252 if result: 253 return True 254 255 return False 256 257 258def KillRunningEmulators(): 259 emulatorPattern = re.compile("(?P<emu>emulator-\d+)") 260 emulatorList = subprocess.check_output(["adb", "devices"]) 261 262 for _, line in enumerate(emulatorList.splitlines()): 263 result = emulatorPattern.search(line) 264 if result: 265 emulatorName = result.group( "emu" ) 266 subprocess.check_call( [ "adb", "-s", emulatorName, "emu", "kill" ] ) 267 268 269def WaitForEmulatorToBoot(): 270 time.sleep(5) 271 subprocess.check_call( [ "adb", "-e", "wait-for-device" ] ) 272 273 print( "Device online; booting..." ) 274 275 bootCompleted = False 276 bootAnimPlaying = True 277 while not bootCompleted or bootAnimPlaying: 278 time.sleep(1) 279 bootCompleted = subprocess.check_output( [ "adb", "-e", "shell", "getprop sys.boot_completed" ] ).strip() == "1" 280 bootAnimPlaying = subprocess.check_output( [ "adb", "-e", "shell", "getprop init.svc.bootanim" ] ).strip() != "stopped" 281 282 print( "Device booted" ) 283 284 285def InitializeEmulator(avd, useExistingEmu): 286 if not useExistingEmu: 287 KillRunningEmulators() 288 289 if not IsAnEmulatorRunning(): 290 # this may not work on windows due to the shell and & 291 subprocess.Popen( "emulator -avd " + avd + " -gpu off &", shell=True ).communicate() 292 293 WaitForEmulatorToBoot() 294 295 296#TEMPORARY: once we have android CI, we will adjust the emulator's CA set as a one-time step and then remove this step 297def BuildAndInstallCertSet(pemSourceDir, buildDir): 298 # android's default cert set does not allow verification of Amazon's cert chain, so we build, install, and use our own set that works 299 certDir = os.path.join( buildDir, "certs" ) 300 pemSourceFile = os.path.join( pemSourceDir, "cacert.pem" ) 301 302 # assume that if the directory exists, then the cert set is valid and we just need to upload 303 if not os.path.exists( certDir ): 304 os.makedirs( certDir ) 305 306 # extract all the certs in curl's master cacert.pem file out into individual .pem files 307 subprocess.check_call( "cat " + pemSourceFile + " | awk '{print > \"" + certDir + "/cert\" (1+n) \".pem\"} /-----END CERTIFICATE-----/ {n++}'", shell = True ) 308 309 # use openssl to transform the certs into the hashname form that curl/openssl expects 310 subprocess.check_call( "c_rehash certs", shell = True, cwd = buildDir ) 311 312 # The root (VeriSign 3) cert in Amazon's chain is missing from curl's master cacert.pem file and needs to be copied manually 313 shutil.copy(os.path.join( pemSourceDir, "certs", "415660c1.0" ), certDir) 314 shutil.copy(os.path.join( pemSourceDir, "certs", "7651b327.0" ), certDir) 315 316 subprocess.check_call( [ "adb", "shell", "rm -rf /data/data/aws." + TestLowerName + "/certs" ] ) 317 subprocess.check_call( [ "adb", "shell", "mkdir /data/data/aws." + TestLowerName + "/certs" ] ) 318 319 # upload all the hashed certs to the emulator 320 certPattern = re.compile(".*\.0$") 321 322 for rootDir, dirNames, fileNames in os.walk( certDir ): 323 for fileName in fileNames: 324 if certPattern.search(fileName): 325 certFileName = os.path.join(rootDir, fileName) 326 subprocess.check_call( [ "adb", "push", certFileName, "/data/data/aws." + TestLowerName + "/certs" ] ) 327 328def UploadTestResources(resourcesDir): 329 for rootDir, dirNames, fileNames in os.walk( resourcesDir ): 330 for fileName in fileNames: 331 resourceFileName = os.path.join( rootDir, fileName ) 332 subprocess.check_call( [ "adb", "push", resourceFileName, os.path.join( "/data/data/aws." + TestLowerName + "/resources", fileName ) ] ) 333 334def UploadAwsSigV4TestSuite(resourceDir): 335 for rootDir, dirNames, fileNames in os.walk( resourceDir ): 336 for fileName in fileNames: 337 resourceFileName = os.path.join( rootDir, fileName ) 338 subDir = os.path.basename( rootDir ) 339 subprocess.check_call( [ "adb", "push", resourceFileName, os.path.join( "/data/data/aws." + TestLowerName + "/resources", subDir, fileName ) ] ) 340 341 342def InstallTests(credentialsFile): 343 subprocess.check_call( [ "adb", "install", "-r", TestName + "/app/build/outputs/apk/app-debug.apk" ] ) 344 subprocess.check_call( [ "adb", "logcat", "-c" ] ) # this doesn't seem to work 345 if credentialsFile and credentialsFile != "": 346 print( "uploading credentials" ) 347 subprocess.check_call( [ "adb", "push", credentialsFile, "/data/data/aws." + TestLowerName + "/.aws/credentials" ] ) 348 349 350def TestsAreRunning(timeStart): 351 shutdownCalledOutput = subprocess.check_output( "adb logcat -t " + timeStart + " *:V | grep \"Shutting down TestActivity\"; exit 0 ", shell = True ) 352 return not shutdownCalledOutput 353 354 355def RunTest(testName): 356 time.sleep(5) 357 print( "Attempting to unlock..." ) 358 subprocess.check_call( [ "adb", "-e", "shell", "input keyevent 82" ] ) 359 360 logTime = datetime.datetime.now() + datetime.timedelta(minutes=-1) # the emulator and the computer do not appear to be in perfect sync 361 logTimeString = logTime.strftime("\"%m-%d %H:%M:%S.000\"") 362 363 time.sleep(5) 364 print( "Attempting to run tests..." ) 365 subprocess.check_call( [ "adb", "shell", "am start -e test " + testName + " -n aws." + TestLowerName + "/aws." + TestLowerName + ".RunSDKTests" ] ) 366 367 time.sleep(10) 368 369 while TestsAreRunning(logTimeString): 370 print( "Tests still running..." ) 371 time.sleep(5) 372 373 print( "Saving logs..." ) 374 subprocess.Popen( "adb logcat -t " + logTimeString + " *:V | grep -a NativeSDK > AndroidTestOutput.txt", shell=True ) 375 376 print( "Cleaning up..." ) 377 subprocess.check_call( [ "adb", "shell", "pm clear aws." + TestLowerName ] ) 378 379 380def DidAllTestsSucceed(): 381 failures = subprocess.check_output( "grep \"FAILED\" AndroidTestOutput.txt ; exit 0", shell = True ) 382 return failures == "" 383 384 385def Main(): 386 args = ParseArguments() 387 388 avd = args[ "avd" ] 389 abi = args[ "abi" ] 390 clean = args[ "clean" ] 391 useExistingEmu = args[ "useExistingEmulator" ] 392 skipBuild = args[ "noBuild" ] 393 credentialsFile = args[ "credentialsFile" ] 394 buildType = args[ "buildType" ] 395 noInstall = args[ "noInstall" ] 396 buildSharedObjects = args[ "so" ] 397 runTest = args[ "runTest" ] 398 stl = args[ "stl" ] 399 400 buildDir = "_build" + buildType 401 installDir = os.path.join( "external", abi ); 402 403 if runTest: 404 avd, abi, clean = ValidateArguments(buildDir, avd, abi, clean, runTest, buildSharedObjects) 405 406 jniDir = SetupJniDirectory(abi, clean) 407 408 if not skipBuild: 409 BuildNative(abi, clean, buildDir, jniDir, installDir, buildType, buildSharedObjects, stl) 410 BuildJava(clean) 411 412 if not runTest: 413 return 0 414 415 print("Starting emulator...") 416 InitializeEmulator(avd, useExistingEmu) 417 418 if not noInstall: 419 print("Installing tests...") 420 InstallTests(credentialsFile) 421 422 print("Installing certs...") 423 BuildAndInstallCertSet("android-build", buildDir) 424 425 print("Uploading test resources") 426 UploadTestResources("aws-cpp-sdk-lambda-integration-tests/resources") 427 428 print("Uploading SigV4 test files") 429 UploadAwsSigV4TestSuite(os.path.join("aws-cpp-sdk-core-tests", "resources", "aws4_testsuite", "aws4_testsuite")) 430 431 print("Running tests...") 432 RunTest( runTest ) 433 434 if not useExistingEmu: 435 KillRunningEmulators() 436 437 if DidAllTestsSucceed(): 438 print( "All tests passed!" ) 439 return 0 440 else: 441 print( "Some tests failed. See AndroidTestOutput.txt" ) 442 return 1 443 444 445 446Main() 447