class MainActivityInvocationStrategy(RunningStrategy): def __init__(self, adbDevice, pathToAndroidManifest): self.adbDevice = adbDevice self.androidManifest=AndroidManifest(pathToAndroidManifest) def run(self, delay=10): mainActivity = self.androidManifest.getMainActivity() if mainActivity: self.adbDevice.startActivityExplicitly(package_name=self.androidManifest.getPackageName(), activity_name=mainActivity) time.sleep(delay)
class MainActivityInvocationStrategy(RunningStrategy): def __init__(self, adbDevice, pathToAndroidManifest): self.adbDevice = adbDevice self.androidManifest = AndroidManifest(pathToAndroidManifest) def run(self, delay=10): mainActivity = self.androidManifest.getMainActivity() if mainActivity: self.adbDevice.startActivityExplicitly( package_name=self.androidManifest.getPackageName(), activity_name=mainActivity) time.sleep(delay)
def initAlreadyInstrApkEnv(self, pathToInstrApk, resultsDir, pathToInstrManifestFile=None): if not apk_utils.checkInputApkFile(pathToInstrApk): logger.error("Provided file [%s] is not a valid apk file!" % pathToInstrApk) return if not os.path.isdir(resultsDir): logger.error("Provided path to results dir [%s] do not point to dir!" % resultsDir) return coverageMetadataFolderPath = os.path.join(resultsDir, self.config.getCoverageMetadataRelativeDir()) if not os.path.isdir(coverageMetadataFolderPath): logger.error("In the results dir [%s] there is no folder with coverage metadata!" % resultsDir) return self.coverageMetadataFolder = coverageMetadataFolderPath if self.config.getCoverageMetadataFilename() not in os.listdir(coverageMetadataFolderPath): logger.error("Cannot find metadata filename in the coverage metadata folder: %s!" % self.coverageMetadataFolder) return self.coverageMetadataFile = os.path.join(self.coverageMetadataFolder, self.config.getCoverageMetadataFilename()) #by default trying to look for a file in the if pathToInstrManifestFile: androidManifestPath = pathToInstrManifestFile else: androidManifestPath = os.path.join(resultsDir, "AndroidManifest.xml") if not os.path.isfile(androidManifestPath): logger.warning("Path [%s] is not pointing to a real file! Leaving pointer to AndroidManifest.xml empty!" % androidManifestPath) return self.instrumentedApk = pathToInstrApk self.apkResultsDir = resultsDir self.runtimeReportsRootDir = self._createDir(resultsDir, self.config.getRuntimeReportsRelativeDir(), False, False) self.androidManifestFile = androidManifestPath self.androidManifest = AndroidManifest(self.androidManifestFile) self.packageName = self.androidManifest.getInstrumentationTargetPackage() self.runnerName = self.androidManifest.getInstrumentationRunnerName() self._bboxStateMachine.start(STATE_VALID_SETTINGS_PROVIDED)
def __init__(self, adbDevice, pathToAndroidManifest): self.adbDevice = adbDevice self.androidManifest = AndroidManifest(pathToAndroidManifest)
class IntentInvocationStrategy(RunningStrategy): def __init__(self, adbDevice, pathToAndroidManifest): self.adbDevice = adbDevice self.androidManifest = AndroidManifest(pathToAndroidManifest) def run(self, delay=10): print "Activities:" activities = self.androidManifest.getActivities() for activity in activities: self._invokeActivityExplicitely(activity) time.sleep(delay) print "Services:" services = self.androidManifest.getServices() for service in services: self._invokeServiceExplicitely(service) time.sleep(delay) print "Receivers:" receivers = self.androidManifest.getReceivers() for receiver in receivers: self._invokeReceiver(receiver) time.sleep(delay) def _invokeActivityExplicitely(self, activity): self.adbDevice.startActivityExplicitly( package_name=self.androidManifest.getPackageName(), activity_name=activity) def _invokeActivity(self, activity): intentFilters = self.androidManifest.getActivityIntentFilters(activity) if not intentFilters: self.adbDevice.startActivityExplicitly( package_name=self.androidManifest.getPackageName(), activity_name=activity) return for filt in intentFilters: action = self._getAction(filt) category = self._getCategory(filt) mimeType = self._getMiMeType(filt) self.adbDevice.startActivityImplicitely(action=action, mimeType=mimeType, category=category) def _invokeServiceExplicitely(self, service): self.adbDevice.startServiceExplicitly( package_name=self.androidManifest.getPackageName(), service_name=service) def _invokeService(self, service): intentFilters = self.androidManifest.getServiceIntentFilters(service) if not intentFilters: self.adbDevice.startServiceExplicitly( package_name=self.androidManifest.getPackageName(), service_name=service) return for filt in intentFilters: action = self._getAction(filt) category = self._getCategory(filt) mimeType = self._getMiMeType(filt) self.adbDevice.startServiceImplicitely(action=action, mimeType=mimeType, category=category) def _invokeReceiver(self, receiver): intentFilters = self.androidManifest.getReceiverIntentFilters(receiver) if not intentFilters: return for filt in intentFilters: action = self._getAction(filt) category = self._getCategory(filt) mimeType = self._getMiMeType(filt) self.adbDevice.sendBroadcast(action=action, mimeType=mimeType, category=category) def _getAction(self, filt): action = None actions = filt.get("action") if actions: action = actions[0] return action def _getCategory(self, filt): category = None categories = filt.get("category") if categories: category = categories[0] else: category = CATEGORY_DEFAULT return category def _getMiMeType(self, filt): mimeType = None mimeTypes = filt.get("mimeType") if mimeTypes: mimeType = mimeTypes[0] return mimeType
def instrumentAndroidManifestFile(self, pathToUnmodifiedFile, pathToModifiedFile=None, addSdCardPermission=True): ''' Adds instrumentation tag with predefined attributes corresponding to our instrumentation classes to the provided manifest file. If instrumentation tag exists, this method substitutes it with appropriate one. Adds (if necessary) to the provided AndroidManifest file permission to write to the external storage. Args: :param pathToUnmodifiedFile: path to the unmodified AndroidManifest.xml file :param pathToModifiedFile: path where to store modified AndroidManifest.xml file. If pathToModifiedFile==None, the initial pathToUnmodifiedFile will be overridden. ''' if not os.path.isfile(pathToUnmodifiedFile): raise IllegalArgumentException("File [%s] does not exist!" % pathToUnmodifiedFile) androidManifest = AndroidManifest(pathAndroidManifest=pathToUnmodifiedFile) packageName = androidManifest.getPackageName() #TODO: think how to substitute these constants later try: androidManifest.addInstrumentation("com.zhauniarovich.bbtester.EmmaInstrumentation", packageName) except ManifestAlreadyInstrumentedException: #removing all existing instrumentation tags and creating our new androidManifest.removeExistingInstrumentation() #TODO: this can throw an exception androidManifest.addInstrumentation("com.zhauniarovich.bbtester.EmmaInstrumentation", packageName) if addSdCardPermission: androidManifest.addUsesPermission("android.permission.WRITE_EXTERNAL_STORAGE") if not pathToModifiedFile or (pathToUnmodifiedFile == pathToModifiedFile): androidManifest.exportManifest(path=None) else: androidManifest.exportManifest(path=pathToModifiedFile)
class BBoxCoverage: PREFIX_ONSTOP = "onstop" PREFIX_ONERROR = "onerror" ''' This class unites all separate classes needed for instrumentation and testing of the instrumented apk and provides a consistent workflow for a developer. ''' # def __init__(self, resultsRootDir=None, pathToBBoxConfigFile="./config/bbox_config.ini"): # ''' # Constructor. # ''' # #getting resultsRootDir # self.resultsRootDir = None # if not resultsRootDir: # self.resultsRootDir = os.path.join(os.getcwd(), ) # else: # self.resultsRootDir = os.path.abspath(resultsRootDir) # # self.config = BBoxConfig(pathToBBoxConfigFile) # self._bboxStateMachine = StateMachine(states=STATES) def __init__(self, pathToBBoxConfigFile="./config/bbox_config.ini"): self.androidManifestFile = None self.instrumentedApk = None self.config = BBoxConfig(pathToBBoxConfigFile) self.bboxInstrumenter = BBoxInstrumenter(self.config) self.bboxExecutor = BBoxExecutor(self.config) self.bboxReporter = BBoxReporter(self.config) self._bboxStateMachine = StateMachine(states=STATES) def getInstrumentedApk(self): return self.instrumentedApk def getPackageName(self): return self.packageName def instrumentApkForCoverage(self, pathToOrigApk, resultsDir=None, tmpDir=None, removeApkTmpDirAfterInstr=True, copyApkToRes=True): ''' Args: :param pathToOrigApk: :param resultsDir: :param tmpDir: Returns: :ret True - if instrumentation was successful, False otherwise ''' self._bboxStateMachine.start(STATE_UNINITIALIZED) valid = self._checkProvidedApk(pathToOrigApk) if not valid: return False self._bboxStateMachine.transitToState(STATE_APK_VALID) resultsRootDir = None if not resultsDir: resultsRootDir = os.path.join(os.getcwd(), RESULTS_RELATIVE_DIR) else: resultsRootDir = os.path.abspath(resultsDir) tmpRootDir = None if not tmpDir: tmpRootDir = os.path.join(os.getcwd(), TMP_RELATIVE_DIR) else: tmpRootDir = os.path.abspath(tmpDir) apkFileName = os.path.splitext(os.path.basename(pathToOrigApk))[0] self.apkTmpDir = self._createDir(tmpDir, apkFileName, False, True) self.apkResultsDir = self._createDir(resultsRootDir, apkFileName, False, True) self.coverageMetadataFolder = self._createDir( self.apkResultsDir, self.config.getCoverageMetadataRelativeDir(), False, True) self.runtimeReportsRootDir = self._createDir( self.apkResultsDir, self.config.getRuntimeReportsRelativeDir(), False, True) self._bboxStateMachine.transitToState(STATE_FOLDERS_CREATED) #coping initial apk file if required if copyApkToRes: shutil.copy2(pathToOrigApk, self.apkResultsDir) #decompiling apk into a folder decompileDir = os.path.join(self.apkTmpDir, self.config.getDecompiledApkRelativeDir()) success = self._decompileApk(self.bboxInstrumenter, pathToOrigApk, decompileDir) if not success: return False self._bboxStateMachine.transitToState(STATE_APK_DECOMPILED) #getting all available dex files dexFilesRelativePaths = self._getDexFilePathsRelativeToDir( decompileDir) if not dexFilesRelativePaths: logger.error("There is no dex files to convert!") return False if "classes.dex" not in dexFilesRelativePaths: # "classes.dex" must be in the decompile root directory logger.error( "There is no classes.dex file found in the list of dex files") return False #converting dex to jar files rawJarFilesRootDir = os.path.join(self.apkTmpDir, self.config.getTmpJarRelativeDir()) jarFilesRelativePaths = self._convertDex2JarFiles( converter=self.bboxInstrumenter, dexFilesRootDir=decompileDir, dexFilesRelativePaths=dexFilesRelativePaths, jarFilesRootDir=rawJarFilesRootDir, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_DEX_CONVERTED_TO_JAR) if "classes.jar" not in jarFilesRelativePaths: #main file is not converted logger.error( "Conversion from classes.dex to classes.jar was not successful!" ) return False #instrumenting available jar files self.coverageMetadataFile = os.path.join( self.coverageMetadataFolder, self.config.getCoverageMetadataFilename()) emmaInstrJarFilesRootDir = os.path.join( self.apkTmpDir, self.config.getInstrumentedFilesRelativeDir()) emmaInstrJarFileRelativePaths = self._instrFilesWithEmma( instrumenter=self.bboxInstrumenter, jarFilesRootDir=rawJarFilesRootDir, jarFilesRelativePaths=jarFilesRelativePaths, instrJarsRootDir=emmaInstrJarFilesRootDir, coverageMetadataFile=self.coverageMetadataFile, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_JARS_INSTRUMENTED) if "classes.jar" not in emmaInstrJarFileRelativePaths: #main file is not instrumented logger.error("Instrumentation of classes.jar was not successful!") return False #converting jar files back to dex files instrDexFilesRelativePaths = self._convertJar2DexWithInstr( converter=self.bboxInstrumenter, instrJarsRootDir=emmaInstrJarFilesRootDir, instrJarFilesRelativePaths=emmaInstrJarFileRelativePaths, finalDexFilesRootDir=decompileDir, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_JAR_CONVERTED_TO_DEX) if "classes.dex" not in instrDexFilesRelativePaths: logger.error( "There is no classes.dex file found in the list of dex files") return False #checking what files have not been converted uninstrumentedFiles = self._getUnInstrFilesRelativePaths( dexFilesRelativePaths, instrDexFilesRelativePaths) if uninstrumentedFiles: logger.debug("The following files were not instrumented: %s" + str(uninstrumentedFiles)) #instrument AndroidManifest.xml decompiledAndroidManifestPath = os.path.join(decompileDir, "AndroidManifest.xml") success = self._instrAndroidManifest(self.bboxInstrumenter, decompiledAndroidManifestPath) if not success: logger.error("Cannot instrument AndroidManifest.xml file!") return False #coping instrumented AndroidManifest.xml to result folder shutil.copy2(decompiledAndroidManifestPath, self.apkResultsDir) self.androidManifestFile = os.path.join(self.apkResultsDir, "AndroidManifest.xml") self._bboxStateMachine.transitToState(STATE_MANIFEST_INSTRUMENTED) compiledApkFilePath = os.path.join( self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getInstrFileSuffix())) success = self._compileApk(self.bboxInstrumenter, decompileDir, compiledApkFilePath) if not success: logger.error("Cannot build apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_BUILD) #need to copy resources into file compiledApkFilePathWithEmmaRes = os.path.join( self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getFinalInstrFileSuffix())) shutil.copy2(compiledApkFilePath, compiledApkFilePathWithEmmaRes) self._putAdditionalResources( apk=compiledApkFilePathWithEmmaRes, resources=self.config.getEmmaResourcesDir()) self._bboxStateMachine.transitToState( STATE_FINAL_INSTRUMENTED_APK_BUILD) signedApkFilePath = os.path.join( self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getSignedFileSuffix())) success = self._signApk(self.bboxInstrumenter, compiledApkFilePathWithEmmaRes, signedApkFilePath) if not success: logger.error("Cannot sign apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_SIGNED) alignedApkFilePath = os.path.join( self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getAlignedFileSuffix())) success = self._alignApk(self.bboxInstrumenter, signedApkFilePath, alignedApkFilePath) if not success: logger.error("Cannot align apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_ALIGNED) #cleaning: if tmp dir needs to be removed after instrumentation if removeApkTmpDirAfterInstr: shutil.rmtree(self.apkTmpDir) self.instrumentedApk = alignedApkFilePath self.androidManifest = AndroidManifest(self.androidManifestFile) self.packageName = self.androidManifest.getInstrumentationTargetPackage( ) self.runnerName = self.androidManifest.getInstrumentationRunnerName() #Final node transition self._bboxStateMachine.transitToState(STATE_APK_INSTRUMENTED) return True def _createDir(self, root, directory, createNew=True, overwrite=False): resDir = os.path.join(root, directory) if createNew: i = 0 while os.path.exists(resDir): i += 1 resDir = os.path.join(root, "%s_%d" % (directory, i)) auxiliary_utils.mkdir(path=resDir, mode=0777, overwrite=overwrite) # resDir = os.path.join(root, directory) # if not overwrite: # i = 0 # while os.path.exists(resDir): # i += 1 # resDir = os.path.join(root, "%s_%d" % (directory, i)) # # auxiliary_utils.mkdir(path=resDir, mode=0777, overwrite=overwrite) return resDir def _createFolder(self, root, dirName, overwrite=False): resultDir = os.path.join(root, dirName) auxiliary_utils.mkdir(path=resultDir, mode=0777, overwrite=overwrite) return resultDir def _checkProvidedApk(self, pathToApk): ''' This methods validates the provided apk file. Raises: ApkIsNotValidException: if the provided apk file is not correct ''' (valid, error) = apk_utils.checkInputApkFile(pathToApk) if not valid: logger.error("Path %s points to the invalid apk file! ERROR:%s" % (pathToApk, error)) return valid return valid def _decompileApk(self, decompiler, apk, outputDir): try: decompiler.decompileApk(apk, outputDir) except ApkCannotBeDecompiledException as e: logger.error(e.msg) return False except: logger.error("Unknown error during decompile process! Exit") return False return True def _convertDex2JarFiles(self, converter, dexFilesRootDir, dexFilesRelativePaths, jarFilesRootDir, proceedOnError=True): jarFilesRelativePaths = [] for dexFileRelativePath in dexFilesRelativePaths: dexFilePath = os.path.join(dexFilesRootDir, dexFileRelativePath) jarFileRelativePath = os.path.splitext( dexFileRelativePath)[0] + ".jar" jarFilePath = os.path.join(jarFilesRootDir, jarFileRelativePath) try: converter.convertDex2Jar(dexFilePath, jarFilePath, overwrite=True) except Dex2JarConvertionError as e: if proceedOnError: logger.warning("Cannot convert [%s] to [%s]. %s" ( dexFilePath, jarFilePath, e.msg)) continue else: raise jarFilesRelativePaths.append(jarFileRelativePath) return jarFilesRelativePaths def _instrFilesWithEmma(self, instrumenter, jarFilesRootDir, jarFilesRelativePaths, instrJarsRootDir, coverageMetadataFile, proceedOnError=True): instrJarFilesRelativePaths = [] for jarFileRelativePath in jarFilesRelativePaths: jarFileAbsPath = os.path.join(jarFilesRootDir, jarFileRelativePath) instrJarRelativeDir = jarFileRelativePath[:jarFileRelativePath. rfind("/") + 1] instrJarFullDir = os.path.join(instrJarsRootDir, instrJarRelativeDir) try: instrumenter.instrumentJarWithEmma( jarFile=jarFileAbsPath, outputFolder=instrJarFullDir, emmaMetadataFile=coverageMetadataFile) except EmmaCannotInstrumentException as e: if proceedOnError: logger.warning("Cannot instrument [%s]. %s" ( jarFileAbsPath, e.msg)) continue else: raise instrJarFilesRelativePaths.append(jarFileRelativePath) return instrJarFilesRelativePaths def _convertJar2DexWithInstr(self, converter, instrJarsRootDir, instrJarFilesRelativePaths, finalDexFilesRootDir, proceedOnError): instrDexFilesRelativePaths = [] for jarFileRelativePath in instrJarFilesRelativePaths: jarFileAbsPath = os.path.join(instrJarsRootDir, jarFileRelativePath) dexFileRelativePath = os.path.splitext( jarFileRelativePath)[0] + ".dex" dexFileAbsPath = os.path.join(finalDexFilesRootDir, dexFileRelativePath) #we instrument main file with auxiliary classes print "jarFileRelativePath: " + jarFileRelativePath try: withFiles = [] #we compile main file with additional instrumentation files #hack: emma copies instrumented jar files into lib folder if jarFileRelativePath == "classes.jar": emmaDevicePath = os.path.join( self.config.getEmmaDir(), self.config.getEmmaDeviceJar()) withFiles.append( self.config. getAndroidSpecificInstrumentationClassesPath()) withFiles.append(emmaDevicePath) converter.convertJar2Dex(jarFile=jarFileAbsPath, dexFile=dexFileAbsPath, withFiles=withFiles, overwrite=True) except Jar2DexConvertionError as e: if proceedOnError: logger.warning("Cannot instrument [%s]. %s" % (jarFileAbsPath, e.msg)) continue else: raise instrDexFilesRelativePaths.append(dexFileRelativePath) return instrDexFilesRelativePaths def _getUnInstrFilesRelativePaths(self, dexFilesRelativePaths, instrDexFilesRelativePaths): uninstrumentedFiles = [] for dexFileRelativePath in dexFilesRelativePaths: if dexFileRelativePath not in instrDexFilesRelativePaths: uninstrumentedFiles.append(dexFileRelativePath) return uninstrumentedFiles def _instrAndroidManifest(self, instrumenter, initAndroidManifest, instrAndroidManifest=None, addSdCardPermission=True): success = True try: instrumenter.instrumentAndroidManifestFile(initAndroidManifest, instrAndroidManifest, addSdCardPermission) except IllegalArgumentException as e: logger.error("Cannot instrument AndroidManifest file. %s" % e.msg) success = False except: logger.error("Cannot instrument AndroidManifest file!") success = False return success def _compileApk(self, compiler, fromDir, apkPath): success = True try: compiler.buildApk(fromDir, apkPath) except ApktoolBuildException as e: logger.error("Cannot build apk! %s" % e.msg) success = False except: logger.error("Cannot build apk!") success = False return success def _putAdditionalResources(self, apk, resources): zip_utils.zipdir(resources, apk) def _signApk(self, signer, unsignedApkFile, signedApkFile): success = True try: signer.signApk(unsignedApkFile, signedApkFile) except SignApkException as e: logger.error("Cannot sign apk! %s" % e.msg) success = False except: logger.error("Cannot sign apk!") success = False return success def _alignApk(self, aligner, unalignedApkFile, alignedApkFile): success = True try: aligner.alignApk(unalignedApkFile, alignedApkFile) except AlignApkException as e: logger.error("Cannot align apk! %s" % e.msg) success = False except: logger.error("Cannot align apk!") success = False return success # def _getMainDexFile(self, directory): # mainDexFile = os.path.join(directory, "classes.dex") # if not os.path.exists(mainDexFile): # logger.error("Cannot find classes.dex file!") # return (False, None) # # return (True, mainDexFile) def _getDexFiles(self, directory): dexFileNames = auxiliary_utils.searchFiles(where=directory, extension="dex") return dexFileNames def _getDexFilePathsRelativeToDir(self, target): dexFileRelativePaths = auxiliary_utils.searchFilesRelativeToDir( target=target, extension="dex") return dexFileRelativePaths ################################################################################ def initAlreadyInstrApkEnv(self, pathToInstrApk, resultsDir, pathToInstrManifestFile=None): if not apk_utils.checkInputApkFile(pathToInstrApk): logger.error("Provided file [%s] is not a valid apk file!" % pathToInstrApk) return if not os.path.isdir(resultsDir): logger.error( "Provided path to results dir [%s] do not point to dir!" % resultsDir) return coverageMetadataFolderPath = os.path.join( resultsDir, self.config.getCoverageMetadataRelativeDir()) if not os.path.isdir(coverageMetadataFolderPath): logger.error( "In the results dir [%s] there is no folder with coverage metadata!" % resultsDir) return self.coverageMetadataFolder = coverageMetadataFolderPath if self.config.getCoverageMetadataFilename() not in os.listdir( coverageMetadataFolderPath): logger.error( "Cannot find metadata filename in the coverage metadata folder: %s!" % self.coverageMetadataFolder) return self.coverageMetadataFile = os.path.join( self.coverageMetadataFolder, self.config.getCoverageMetadataFilename()) #by default trying to look for a file in the if pathToInstrManifestFile: androidManifestPath = pathToInstrManifestFile else: androidManifestPath = os.path.join(resultsDir, "AndroidManifest.xml") if not os.path.isfile(androidManifestPath): logger.warning( "Path [%s] is not pointing to a real file! Leaving pointer to AndroidManifest.xml empty!" % androidManifestPath) return self.instrumentedApk = pathToInstrApk self.apkResultsDir = resultsDir self.runtimeReportsRootDir = self._createDir( resultsDir, self.config.getRuntimeReportsRelativeDir(), False, False) self.androidManifestFile = androidManifestPath self.androidManifest = AndroidManifest(self.androidManifestFile) self.packageName = self.androidManifest.getInstrumentationTargetPackage( ) self.runnerName = self.androidManifest.getInstrumentationRunnerName() self._bboxStateMachine.start(STATE_VALID_SETTINGS_PROVIDED) def installApkOnDevice(self): if not self._bboxStateMachine.isTransitionPossible( STATE_APK_INSTALLED): logger.error( "Cannot install apk on device because the environment is not initialized!" ) return #selecting device for execution self.bboxExecutor.selectExecutionDevice() try: self.bboxExecutor.installApkOnDevice(self.instrumentedApk) except ApkCannotBeInstalledException as e: logger.error("Cannot install instrumented apk. %s" % e.msg) return self._bboxStateMachine.transitToState(STATE_APK_INSTALLED) def startTesting(self): if not self._bboxStateMachine.isTransitionPossible( STATE_APK_TEST_STARTED): logger.error("Cannot start testing apk on a device!") return self.deviceReportFolder = os.path.join(DEVICE_REPORT_FOLDER_PATH, self.packageName) self.bboxExecutor.selectExecutionDevice() self.bboxExecutor.startOndeviceTesting( packageName=self.packageName, runnerName=self.runnerName, coverage=True, reportFolder=self.deviceReportFolder, proceedOnError=True, generateCoverageReportOnError=True) self._bboxStateMachine.transitToState(STATE_APK_TEST_STARTED) def stopTesting(self, localReportFolderName=None, paramsToWrite=None): if not self._bboxStateMachine.isTransitionPossible( STATE_APK_FINISHED_TESTING): logger.error("Cannot stop testing because it is not started!") return # currentDateTime = datetime.datetime.now() # reportTimePrefix = currentDateTime.strftime("%Y_%m_%d__%H_%M_%S") # coverageReportName = "%s___%s" % (reportTimePrefix, "coverage.ec") # reportFileOnDevice = "%s/%s" % (DEVICE_REPORT_FOLDER_PATH, coverageReportName) # reportLocally = os.path.join(self.runtimeReportsRootDir, coverageReportName) if not localReportFolderName: localReportFolderName = "test" localReportFolder = self._createDir(self.runtimeReportsRootDir, localReportFolderName, True, False) self.bboxExecutor.stopOndeviceTesting(cancelAnalysis=False) time.sleep(3) #waiting for several seconds for report to be generated # success = self.bboxExecutor.getFileFromDevice(reportFileOnDevice, reportLocally) success = self.bboxExecutor.getFileFromDevice(self.deviceReportFolder, localReportFolder) if not success: self.bboxExecutor.removeFile(self.deviceReportFolder) return None if paramsToWrite: params_config = ConfigParser.ConfigParser() params_config.add_section(PARAMS_SECTION) for param in iteritems(paramsToWrite): params_config.set(PARAMS_SECTION, param[0], param[1]) with open(os.path.join(localReportFolder, "parameters.txt"), "w") as param_file: params_config.write(param_file) self.bboxExecutor.removeFile(self.deviceReportFolder) self._bboxStateMachine.transitToState(STATE_APK_FINISHED_TESTING) return localReportFolder def generateReport(self, reportFiles=[], reportName=None, reportType=EMMA_REPORT.XML): if not reportFiles: logger.error("No report files are provided!") return self.bboxReporter.cleanMetaFiles() self.bboxReporter.cleanReportFiles() self.bboxReporter.addMetaFile(self.coverageMetadataFile) for rFile in reportFiles: self.bboxReporter.addReportFile(rFile) reportsRoot = os.path.join(self.apkResultsDir, self.config.getReportsRelativeDir()) where = self._createReportResultsDir(reportsRoot, "report_%s" % reportType) self.bboxReporter.generateEmmaReport(where, reportName, reportType) def _createReportResultsDir(self, reportsRoot, reportDirName): i = 0 resultsDir = os.path.join(reportsRoot, reportDirName) while os.path.exists(resultsDir): i += 1 resultsDir = os.path.join(reportsRoot, "%s_%d" % (reportDirName, i)) auxiliary_utils.mkdir(path=resultsDir, mode=0777, overwrite=False) return resultsDir def uninstallPackage(self): self.bboxExecutor.uninstallPackage(packageName=self.packageName, keepData=False) self._bboxStateMachine.stop() @staticmethod def getCoverageReportsFromFolderWithPrefix(folder, prefix): if not os.path.exists(folder): return None reports = [] for file in os.listdir(folder): if file.endswith(".ec") and file.startswith(prefix): reports.append(os.path.join(folder, file)) return reports
def initAlreadyInstrApkEnv(self, pathToInstrApk, resultsDir, pathToInstrManifestFile=None): if not apk_utils.checkInputApkFile(pathToInstrApk): logger.error("Provided file [%s] is not a valid apk file!" % pathToInstrApk) return if not os.path.isdir(resultsDir): logger.error( "Provided path to results dir [%s] do not point to dir!" % resultsDir) return coverageMetadataFolderPath = os.path.join( resultsDir, self.config.getCoverageMetadataRelativeDir()) if not os.path.isdir(coverageMetadataFolderPath): logger.error( "In the results dir [%s] there is no folder with coverage metadata!" % resultsDir) return self.coverageMetadataFolder = coverageMetadataFolderPath if self.config.getCoverageMetadataFilename() not in os.listdir( coverageMetadataFolderPath): logger.error( "Cannot find metadata filename in the coverage metadata folder: %s!" % self.coverageMetadataFolder) return self.coverageMetadataFile = os.path.join( self.coverageMetadataFolder, self.config.getCoverageMetadataFilename()) #by default trying to look for a file in the if pathToInstrManifestFile: androidManifestPath = pathToInstrManifestFile else: androidManifestPath = os.path.join(resultsDir, "AndroidManifest.xml") if not os.path.isfile(androidManifestPath): logger.warning( "Path [%s] is not pointing to a real file! Leaving pointer to AndroidManifest.xml empty!" % androidManifestPath) return self.instrumentedApk = pathToInstrApk self.apkResultsDir = resultsDir self.runtimeReportsRootDir = self._createDir( resultsDir, self.config.getRuntimeReportsRelativeDir(), False, False) self.androidManifestFile = androidManifestPath self.androidManifest = AndroidManifest(self.androidManifestFile) self.packageName = self.androidManifest.getInstrumentationTargetPackage( ) self.runnerName = self.androidManifest.getInstrumentationRunnerName() self._bboxStateMachine.start(STATE_VALID_SETTINGS_PROVIDED)
def instrumentApkForCoverage(self, pathToOrigApk, resultsDir=None, tmpDir=None, removeApkTmpDirAfterInstr=True, copyApkToRes=True): ''' Args: :param pathToOrigApk: :param resultsDir: :param tmpDir: Returns: :ret True - if instrumentation was successful, False otherwise ''' self._bboxStateMachine.start(STATE_UNINITIALIZED) valid = self._checkProvidedApk(pathToOrigApk) if not valid: return False self._bboxStateMachine.transitToState(STATE_APK_VALID) resultsRootDir = None if not resultsDir: resultsRootDir = os.path.join(os.getcwd(), RESULTS_RELATIVE_DIR) else: resultsRootDir = os.path.abspath(resultsDir) tmpRootDir = None if not tmpDir: tmpRootDir = os.path.join(os.getcwd(), TMP_RELATIVE_DIR) else: tmpRootDir = os.path.abspath(tmpDir) apkFileName = os.path.splitext(os.path.basename(pathToOrigApk))[0] self.apkTmpDir = self._createDir(tmpDir, apkFileName, False, True) self.apkResultsDir = self._createDir(resultsRootDir, apkFileName, False, True) self.coverageMetadataFolder = self._createDir( self.apkResultsDir, self.config.getCoverageMetadataRelativeDir(), False, True) self.runtimeReportsRootDir = self._createDir( self.apkResultsDir, self.config.getRuntimeReportsRelativeDir(), False, True) self._bboxStateMachine.transitToState(STATE_FOLDERS_CREATED) #coping initial apk file if required if copyApkToRes: shutil.copy2(pathToOrigApk, self.apkResultsDir) #decompiling apk into a folder decompileDir = os.path.join(self.apkTmpDir, self.config.getDecompiledApkRelativeDir()) success = self._decompileApk(self.bboxInstrumenter, pathToOrigApk, decompileDir) if not success: return False self._bboxStateMachine.transitToState(STATE_APK_DECOMPILED) #getting all available dex files dexFilesRelativePaths = self._getDexFilePathsRelativeToDir( decompileDir) if not dexFilesRelativePaths: logger.error("There is no dex files to convert!") return False if "classes.dex" not in dexFilesRelativePaths: # "classes.dex" must be in the decompile root directory logger.error( "There is no classes.dex file found in the list of dex files") return False #converting dex to jar files rawJarFilesRootDir = os.path.join(self.apkTmpDir, self.config.getTmpJarRelativeDir()) jarFilesRelativePaths = self._convertDex2JarFiles( converter=self.bboxInstrumenter, dexFilesRootDir=decompileDir, dexFilesRelativePaths=dexFilesRelativePaths, jarFilesRootDir=rawJarFilesRootDir, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_DEX_CONVERTED_TO_JAR) if "classes.jar" not in jarFilesRelativePaths: #main file is not converted logger.error( "Conversion from classes.dex to classes.jar was not successful!" ) return False #instrumenting available jar files self.coverageMetadataFile = os.path.join( self.coverageMetadataFolder, self.config.getCoverageMetadataFilename()) emmaInstrJarFilesRootDir = os.path.join( self.apkTmpDir, self.config.getInstrumentedFilesRelativeDir()) emmaInstrJarFileRelativePaths = self._instrFilesWithEmma( instrumenter=self.bboxInstrumenter, jarFilesRootDir=rawJarFilesRootDir, jarFilesRelativePaths=jarFilesRelativePaths, instrJarsRootDir=emmaInstrJarFilesRootDir, coverageMetadataFile=self.coverageMetadataFile, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_JARS_INSTRUMENTED) if "classes.jar" not in emmaInstrJarFileRelativePaths: #main file is not instrumented logger.error("Instrumentation of classes.jar was not successful!") return False #converting jar files back to dex files instrDexFilesRelativePaths = self._convertJar2DexWithInstr( converter=self.bboxInstrumenter, instrJarsRootDir=emmaInstrJarFilesRootDir, instrJarFilesRelativePaths=emmaInstrJarFileRelativePaths, finalDexFilesRootDir=decompileDir, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_JAR_CONVERTED_TO_DEX) if "classes.dex" not in instrDexFilesRelativePaths: logger.error( "There is no classes.dex file found in the list of dex files") return False #checking what files have not been converted uninstrumentedFiles = self._getUnInstrFilesRelativePaths( dexFilesRelativePaths, instrDexFilesRelativePaths) if uninstrumentedFiles: logger.debug("The following files were not instrumented: %s" + str(uninstrumentedFiles)) #instrument AndroidManifest.xml decompiledAndroidManifestPath = os.path.join(decompileDir, "AndroidManifest.xml") success = self._instrAndroidManifest(self.bboxInstrumenter, decompiledAndroidManifestPath) if not success: logger.error("Cannot instrument AndroidManifest.xml file!") return False #coping instrumented AndroidManifest.xml to result folder shutil.copy2(decompiledAndroidManifestPath, self.apkResultsDir) self.androidManifestFile = os.path.join(self.apkResultsDir, "AndroidManifest.xml") self._bboxStateMachine.transitToState(STATE_MANIFEST_INSTRUMENTED) compiledApkFilePath = os.path.join( self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getInstrFileSuffix())) success = self._compileApk(self.bboxInstrumenter, decompileDir, compiledApkFilePath) if not success: logger.error("Cannot build apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_BUILD) #need to copy resources into file compiledApkFilePathWithEmmaRes = os.path.join( self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getFinalInstrFileSuffix())) shutil.copy2(compiledApkFilePath, compiledApkFilePathWithEmmaRes) self._putAdditionalResources( apk=compiledApkFilePathWithEmmaRes, resources=self.config.getEmmaResourcesDir()) self._bboxStateMachine.transitToState( STATE_FINAL_INSTRUMENTED_APK_BUILD) signedApkFilePath = os.path.join( self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getSignedFileSuffix())) success = self._signApk(self.bboxInstrumenter, compiledApkFilePathWithEmmaRes, signedApkFilePath) if not success: logger.error("Cannot sign apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_SIGNED) alignedApkFilePath = os.path.join( self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getAlignedFileSuffix())) success = self._alignApk(self.bboxInstrumenter, signedApkFilePath, alignedApkFilePath) if not success: logger.error("Cannot align apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_ALIGNED) #cleaning: if tmp dir needs to be removed after instrumentation if removeApkTmpDirAfterInstr: shutil.rmtree(self.apkTmpDir) self.instrumentedApk = alignedApkFilePath self.androidManifest = AndroidManifest(self.androidManifestFile) self.packageName = self.androidManifest.getInstrumentationTargetPackage( ) self.runnerName = self.androidManifest.getInstrumentationRunnerName() #Final node transition self._bboxStateMachine.transitToState(STATE_APK_INSTRUMENTED) return True
def __init__(self, adbDevice, pathToAndroidManifest): self.adbDevice = adbDevice self.androidManifest=AndroidManifest(pathToAndroidManifest)
class IntentInvocationStrategy(RunningStrategy): def __init__(self, adbDevice, pathToAndroidManifest): self.adbDevice = adbDevice self.androidManifest=AndroidManifest(pathToAndroidManifest) def run(self, delay=10): print "Activities:" activities = self.androidManifest.getActivities() for activity in activities: self._invokeActivityExplicitely(activity) time.sleep(delay) print "Services:" services = self.androidManifest.getServices() for service in services: self._invokeServiceExplicitely(service) time.sleep(delay) print "Receivers:" receivers = self.androidManifest.getReceivers() for receiver in receivers: self._invokeReceiver(receiver) time.sleep(delay) def _invokeActivityExplicitely(self, activity): self.adbDevice.startActivityExplicitly(package_name=self.androidManifest.getPackageName(), activity_name=activity) def _invokeActivity(self, activity): intentFilters = self.androidManifest.getActivityIntentFilters(activity) if not intentFilters: self.adbDevice.startActivityExplicitly(package_name=self.androidManifest.getPackageName(), activity_name=activity) return for filt in intentFilters: action = self._getAction(filt) category = self._getCategory(filt) mimeType = self._getMiMeType(filt) self.adbDevice.startActivityImplicitely(action=action, mimeType=mimeType, category=category) def _invokeServiceExplicitely(self, service): self.adbDevice.startServiceExplicitly(package_name=self.androidManifest.getPackageName(), service_name=service) def _invokeService(self, service): intentFilters = self.androidManifest.getServiceIntentFilters(service) if not intentFilters: self.adbDevice.startServiceExplicitly(package_name=self.androidManifest.getPackageName(), service_name=service) return for filt in intentFilters: action = self._getAction(filt) category = self._getCategory(filt) mimeType = self._getMiMeType(filt) self.adbDevice.startServiceImplicitely(action=action, mimeType=mimeType, category=category) def _invokeReceiver(self, receiver): intentFilters = self.androidManifest.getReceiverIntentFilters(receiver) if not intentFilters: return for filt in intentFilters: action = self._getAction(filt) category = self._getCategory(filt) mimeType = self._getMiMeType(filt) self.adbDevice.sendBroadcast(action=action, mimeType=mimeType, category=category) def _getAction(self, filt): action = None actions = filt.get("action") if actions: action = actions[0] return action def _getCategory(self, filt): category = None categories = filt.get("category") if categories: category = categories[0] else: category = CATEGORY_DEFAULT return category def _getMiMeType(self, filt): mimeType = None mimeTypes = filt.get("mimeType") if mimeTypes: mimeType = mimeTypes[0] return mimeType
class BBoxCoverage: PREFIX_ONSTOP = "onstop"; PREFIX_ONERROR = "onerror"; ''' This class unites all separate classes needed for instrumentation and testing of the instrumented apk and provides a consistent workflow for a developer. ''' # def __init__(self, resultsRootDir=None, pathToBBoxConfigFile="./config/bbox_config.ini"): # ''' # Constructor. # ''' # #getting resultsRootDir # self.resultsRootDir = None # if not resultsRootDir: # self.resultsRootDir = os.path.join(os.getcwd(), ) # else: # self.resultsRootDir = os.path.abspath(resultsRootDir) # # self.config = BBoxConfig(pathToBBoxConfigFile) # self._bboxStateMachine = StateMachine(states=STATES) def __init__(self, pathToBBoxConfigFile="./config/bbox_config.ini"): self.androidManifestFile = None self.instrumentedApk = None self.config = BBoxConfig(pathToBBoxConfigFile) self.bboxInstrumenter = BBoxInstrumenter(self.config) self.bboxExecutor = BBoxExecutor(self.config) self.bboxReporter = BBoxReporter(self.config) self._bboxStateMachine = StateMachine(states=STATES) def getInstrumentedApk(self): return self.instrumentedApk def getPackageName(self): return self.packageName def instrumentApkForCoverage(self, pathToOrigApk, resultsDir=None, tmpDir=None, removeApkTmpDirAfterInstr=True, copyApkToRes = True): ''' Args: :param pathToOrigApk: :param resultsDir: :param tmpDir: Returns: :ret True - if instrumentation was successful, False otherwise ''' self._bboxStateMachine.start(STATE_UNINITIALIZED) valid = self._checkProvidedApk(pathToOrigApk) if not valid: return False self._bboxStateMachine.transitToState(STATE_APK_VALID) resultsRootDir = None if not resultsDir: resultsRootDir = os.path.join(os.getcwd(), RESULTS_RELATIVE_DIR) else: resultsRootDir = os.path.abspath(resultsDir) tmpRootDir = None if not tmpDir: tmpRootDir = os.path.join(os.getcwd(), TMP_RELATIVE_DIR) else: tmpRootDir = os.path.abspath(tmpDir) apkFileName = os.path.splitext(os.path.basename(pathToOrigApk))[0] self.apkTmpDir = self._createDir(tmpDir, apkFileName, False, True) self.apkResultsDir = self._createDir(resultsRootDir, apkFileName, False, True) self.coverageMetadataFolder = self._createDir(self.apkResultsDir, self.config.getCoverageMetadataRelativeDir(), False, True) self.runtimeReportsRootDir = self._createDir(self.apkResultsDir, self.config.getRuntimeReportsRelativeDir(), False, True) self._bboxStateMachine.transitToState(STATE_FOLDERS_CREATED) #coping initial apk file if required if copyApkToRes: shutil.copy2(pathToOrigApk, self.apkResultsDir) #decompiling apk into a folder decompileDir = os.path.join(self.apkTmpDir, self.config.getDecompiledApkRelativeDir()) success = self._decompileApk(self.bboxInstrumenter, pathToOrigApk, decompileDir) if not success: return False self._bboxStateMachine.transitToState(STATE_APK_DECOMPILED) #getting all available dex files dexFilesRelativePaths = self._getDexFilePathsRelativeToDir(decompileDir) if not dexFilesRelativePaths: logger.error("There is no dex files to convert!") return False if "classes.dex" not in dexFilesRelativePaths: # "classes.dex" must be in the decompile root directory logger.error("There is no classes.dex file found in the list of dex files") return False #converting dex to jar files rawJarFilesRootDir = os.path.join(self.apkTmpDir, self.config.getTmpJarRelativeDir()) jarFilesRelativePaths = self._convertDex2JarFiles( converter=self.bboxInstrumenter, dexFilesRootDir=decompileDir, dexFilesRelativePaths=dexFilesRelativePaths, jarFilesRootDir=rawJarFilesRootDir, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_DEX_CONVERTED_TO_JAR) if "classes.jar" not in jarFilesRelativePaths: #main file is not converted logger.error("Conversion from classes.dex to classes.jar was not successful!") return False #instrumenting available jar files self.coverageMetadataFile = os.path.join(self.coverageMetadataFolder, self.config.getCoverageMetadataFilename()) emmaInstrJarFilesRootDir = os.path.join(self.apkTmpDir, self.config.getInstrumentedFilesRelativeDir()) emmaInstrJarFileRelativePaths = self._instrFilesWithEmma( instrumenter=self.bboxInstrumenter, jarFilesRootDir=rawJarFilesRootDir, jarFilesRelativePaths=jarFilesRelativePaths, instrJarsRootDir=emmaInstrJarFilesRootDir, coverageMetadataFile=self.coverageMetadataFile, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_JARS_INSTRUMENTED) if "classes.jar" not in emmaInstrJarFileRelativePaths: #main file is not instrumented logger.error("Instrumentation of classes.jar was not successful!") return False #converting jar files back to dex files instrDexFilesRelativePaths = self._convertJar2DexWithInstr( converter=self.bboxInstrumenter, instrJarsRootDir=emmaInstrJarFilesRootDir, instrJarFilesRelativePaths=emmaInstrJarFileRelativePaths, finalDexFilesRootDir=decompileDir, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_JAR_CONVERTED_TO_DEX) if "classes.dex" not in instrDexFilesRelativePaths: logger.error("There is no classes.dex file found in the list of dex files") return False #checking what files have not been converted uninstrumentedFiles = self._getUnInstrFilesRelativePaths(dexFilesRelativePaths, instrDexFilesRelativePaths) if uninstrumentedFiles: logger.debug("The following files were not instrumented: %s" + str(uninstrumentedFiles)) #instrument AndroidManifest.xml decompiledAndroidManifestPath = os.path.join(decompileDir, "AndroidManifest.xml") success = self._instrAndroidManifest(self.bboxInstrumenter, decompiledAndroidManifestPath) if not success: logger.error("Cannot instrument AndroidManifest.xml file!") return False #coping instrumented AndroidManifest.xml to result folder shutil.copy2(decompiledAndroidManifestPath, self.apkResultsDir) self.androidManifestFile = os.path.join(self.apkResultsDir, "AndroidManifest.xml") self._bboxStateMachine.transitToState(STATE_MANIFEST_INSTRUMENTED) compiledApkFilePath = os.path.join(self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getInstrFileSuffix())) success = self._compileApk(self.bboxInstrumenter, decompileDir, compiledApkFilePath) if not success: logger.error("Cannot build apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_BUILD) #need to copy resources into file compiledApkFilePathWithEmmaRes = os.path.join(self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getFinalInstrFileSuffix())) shutil.copy2(compiledApkFilePath, compiledApkFilePathWithEmmaRes) self._putAdditionalResources(apk=compiledApkFilePathWithEmmaRes, resources=self.config.getEmmaResourcesDir()) self._bboxStateMachine.transitToState(STATE_FINAL_INSTRUMENTED_APK_BUILD) signedApkFilePath = os.path.join(self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getSignedFileSuffix())) success = self._signApk(self.bboxInstrumenter, compiledApkFilePathWithEmmaRes, signedApkFilePath) if not success: logger.error("Cannot sign apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_SIGNED) alignedApkFilePath = os.path.join(self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getAlignedFileSuffix())) success = self._alignApk(self.bboxInstrumenter, signedApkFilePath, alignedApkFilePath) if not success: logger.error("Cannot align apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_ALIGNED) #cleaning: if tmp dir needs to be removed after instrumentation if removeApkTmpDirAfterInstr: shutil.rmtree(self.apkTmpDir) self.instrumentedApk = alignedApkFilePath self.androidManifest = AndroidManifest(self.androidManifestFile) self.packageName = self.androidManifest.getInstrumentationTargetPackage() self.runnerName = self.androidManifest.getInstrumentationRunnerName() #Final node transition self._bboxStateMachine.transitToState(STATE_APK_INSTRUMENTED) return True def _createDir(self, root, directory, createNew=True, overwrite=False): resDir = os.path.join(root, directory) if createNew: i = 0 while os.path.exists(resDir): i += 1 resDir = os.path.join(root, "%s_%d" % (directory, i)) auxiliary_utils.mkdir(path=resDir, mode=0777, overwrite=overwrite) # resDir = os.path.join(root, directory) # if not overwrite: # i = 0 # while os.path.exists(resDir): # i += 1 # resDir = os.path.join(root, "%s_%d" % (directory, i)) # # auxiliary_utils.mkdir(path=resDir, mode=0777, overwrite=overwrite) return resDir def _createFolder(self, root, dirName, overwrite=False): resultDir = os.path.join(root, dirName) auxiliary_utils.mkdir(path=resultDir, mode=0777, overwrite=overwrite) return resultDir def _checkProvidedApk(self, pathToApk): ''' This methods validates the provided apk file. Raises: ApkIsNotValidException: if the provided apk file is not correct ''' (valid, error) = apk_utils.checkInputApkFile(pathToApk) if not valid: logger.error("Path %s points to the invalid apk file! ERROR:%s" % (pathToApk, error)) return valid return valid def _decompileApk(self, decompiler, apk, outputDir): try: decompiler.decompileApk(apk, outputDir) except ApkCannotBeDecompiledException as e: logger.error(e.msg) return False except: logger.error("Unknown error during decompile process! Exit") return False return True def _convertDex2JarFiles(self, converter, dexFilesRootDir, dexFilesRelativePaths, jarFilesRootDir, proceedOnError=True): jarFilesRelativePaths = [] for dexFileRelativePath in dexFilesRelativePaths: dexFilePath = os.path.join(dexFilesRootDir, dexFileRelativePath) jarFileRelativePath = os.path.splitext(dexFileRelativePath)[0] + ".jar" jarFilePath = os.path.join(jarFilesRootDir, jarFileRelativePath) try: converter.convertDex2Jar(dexFilePath, jarFilePath, overwrite=True) except Dex2JarConvertionError as e: if proceedOnError: logger.warning("Cannot convert [%s] to [%s]. %s" (dexFilePath, jarFilePath, e.msg)) continue else: raise jarFilesRelativePaths.append(jarFileRelativePath) return jarFilesRelativePaths def _instrFilesWithEmma(self, instrumenter, jarFilesRootDir, jarFilesRelativePaths, instrJarsRootDir, coverageMetadataFile, proceedOnError=True): instrJarFilesRelativePaths = [] for jarFileRelativePath in jarFilesRelativePaths: jarFileAbsPath = os.path.join(jarFilesRootDir, jarFileRelativePath) instrJarRelativeDir = jarFileRelativePath[:jarFileRelativePath.rfind("/")+1] instrJarFullDir = os.path.join(instrJarsRootDir, instrJarRelativeDir) try: instrumenter.instrumentJarWithEmma(jarFile=jarFileAbsPath, outputFolder=instrJarFullDir, emmaMetadataFile=coverageMetadataFile) except EmmaCannotInstrumentException as e: if proceedOnError: logger.warning("Cannot instrument [%s]. %s" (jarFileAbsPath, e.msg)) continue else: raise instrJarFilesRelativePaths.append(jarFileRelativePath) return instrJarFilesRelativePaths def _convertJar2DexWithInstr(self, converter, instrJarsRootDir, instrJarFilesRelativePaths, finalDexFilesRootDir, proceedOnError): instrDexFilesRelativePaths = [] for jarFileRelativePath in instrJarFilesRelativePaths: jarFileAbsPath = os.path.join(instrJarsRootDir, jarFileRelativePath) dexFileRelativePath = os.path.splitext(jarFileRelativePath)[0] + ".dex" dexFileAbsPath = os.path.join(finalDexFilesRootDir, dexFileRelativePath) #we instrument main file with auxiliary classes print "jarFileRelativePath: " + jarFileRelativePath try: withFiles = [] #we compile main file with additional instrumentation files #hack: emma copies instrumented jar files into lib folder if jarFileRelativePath == "classes.jar": emmaDevicePath = os.path.join(self.config.getEmmaDir(), self.config.getEmmaDeviceJar()) withFiles.append(self.config.getAndroidSpecificInstrumentationClassesPath()) withFiles.append(emmaDevicePath) converter.convertJar2Dex(jarFile=jarFileAbsPath, dexFile=dexFileAbsPath, withFiles=withFiles, overwrite=True) except Jar2DexConvertionError as e: if proceedOnError: logger.warning("Cannot instrument [%s]. %s" % (jarFileAbsPath, e.msg)) continue else: raise instrDexFilesRelativePaths.append(dexFileRelativePath) return instrDexFilesRelativePaths def _getUnInstrFilesRelativePaths(self, dexFilesRelativePaths, instrDexFilesRelativePaths): uninstrumentedFiles = [] for dexFileRelativePath in dexFilesRelativePaths: if dexFileRelativePath not in instrDexFilesRelativePaths: uninstrumentedFiles.append(dexFileRelativePath) return uninstrumentedFiles def _instrAndroidManifest(self, instrumenter, initAndroidManifest, instrAndroidManifest=None, addSdCardPermission=True): success = True try: instrumenter.instrumentAndroidManifestFile(initAndroidManifest, instrAndroidManifest, addSdCardPermission) except IllegalArgumentException as e: logger.error("Cannot instrument AndroidManifest file. %s" % e.msg) success = False except: logger.error("Cannot instrument AndroidManifest file!") success = False return success def _compileApk(self, compiler, fromDir, apkPath): success = True try: compiler.buildApk(fromDir, apkPath) except ApktoolBuildException as e: logger.error("Cannot build apk! %s" % e.msg) success = False except: logger.error("Cannot build apk!") success = False return success def _putAdditionalResources(self, apk, resources): zip_utils.zipdir(resources, apk) def _signApk(self, signer, unsignedApkFile, signedApkFile): success = True try: signer.signApk(unsignedApkFile, signedApkFile) except SignApkException as e: logger.error("Cannot sign apk! %s" % e.msg) success = False except: logger.error("Cannot sign apk!") success = False return success def _alignApk(self, aligner, unalignedApkFile, alignedApkFile): success = True try: aligner.alignApk(unalignedApkFile, alignedApkFile) except AlignApkException as e: logger.error("Cannot align apk! %s" % e.msg) success = False except: logger.error("Cannot align apk!") success = False return success # def _getMainDexFile(self, directory): # mainDexFile = os.path.join(directory, "classes.dex") # if not os.path.exists(mainDexFile): # logger.error("Cannot find classes.dex file!") # return (False, None) # # return (True, mainDexFile) def _getDexFiles(self, directory): dexFileNames = auxiliary_utils.searchFiles(where=directory, extension="dex") return dexFileNames def _getDexFilePathsRelativeToDir(self, target): dexFileRelativePaths = auxiliary_utils.searchFilesRelativeToDir(target=target, extension="dex") return dexFileRelativePaths ################################################################################ def initAlreadyInstrApkEnv(self, pathToInstrApk, resultsDir, pathToInstrManifestFile=None): if not apk_utils.checkInputApkFile(pathToInstrApk): logger.error("Provided file [%s] is not a valid apk file!" % pathToInstrApk) return if not os.path.isdir(resultsDir): logger.error("Provided path to results dir [%s] do not point to dir!" % resultsDir) return coverageMetadataFolderPath = os.path.join(resultsDir, self.config.getCoverageMetadataRelativeDir()) if not os.path.isdir(coverageMetadataFolderPath): logger.error("In the results dir [%s] there is no folder with coverage metadata!" % resultsDir) return self.coverageMetadataFolder = coverageMetadataFolderPath if self.config.getCoverageMetadataFilename() not in os.listdir(coverageMetadataFolderPath): logger.error("Cannot find metadata filename in the coverage metadata folder: %s!" % self.coverageMetadataFolder) return self.coverageMetadataFile = os.path.join(self.coverageMetadataFolder, self.config.getCoverageMetadataFilename()) #by default trying to look for a file in the if pathToInstrManifestFile: androidManifestPath = pathToInstrManifestFile else: androidManifestPath = os.path.join(resultsDir, "AndroidManifest.xml") if not os.path.isfile(androidManifestPath): logger.warning("Path [%s] is not pointing to a real file! Leaving pointer to AndroidManifest.xml empty!" % androidManifestPath) return self.instrumentedApk = pathToInstrApk self.apkResultsDir = resultsDir self.runtimeReportsRootDir = self._createDir(resultsDir, self.config.getRuntimeReportsRelativeDir(), False, False) self.androidManifestFile = androidManifestPath self.androidManifest = AndroidManifest(self.androidManifestFile) self.packageName = self.androidManifest.getInstrumentationTargetPackage() self.runnerName = self.androidManifest.getInstrumentationRunnerName() self._bboxStateMachine.start(STATE_VALID_SETTINGS_PROVIDED) def installApkOnDevice(self): if not self._bboxStateMachine.isTransitionPossible(STATE_APK_INSTALLED): logger.error("Cannot install apk on device because the environment is not initialized!") return #selecting device for execution self.bboxExecutor.selectExecutionDevice() try: self.bboxExecutor.installApkOnDevice(self.instrumentedApk) except ApkCannotBeInstalledException as e: logger.error("Cannot install instrumented apk. %s" % e.msg) return self._bboxStateMachine.transitToState(STATE_APK_INSTALLED) def startTesting(self): if not self._bboxStateMachine.isTransitionPossible(STATE_APK_TEST_STARTED): logger.error("Cannot start testing apk on a device!") return self.deviceReportFolder = os.path.join(DEVICE_REPORT_FOLDER_PATH, self.packageName) self.bboxExecutor.selectExecutionDevice() self.bboxExecutor.startOndeviceTesting(packageName=self.packageName, runnerName=self.runnerName, coverage=True, reportFolder=self.deviceReportFolder, proceedOnError=True, generateCoverageReportOnError=True) self._bboxStateMachine.transitToState(STATE_APK_TEST_STARTED) def stopTesting(self, localReportFolderName=None, paramsToWrite=None): if not self._bboxStateMachine.isTransitionPossible(STATE_APK_FINISHED_TESTING): logger.error("Cannot stop testing because it is not started!") return # currentDateTime = datetime.datetime.now() # reportTimePrefix = currentDateTime.strftime("%Y_%m_%d__%H_%M_%S") # coverageReportName = "%s___%s" % (reportTimePrefix, "coverage.ec") # reportFileOnDevice = "%s/%s" % (DEVICE_REPORT_FOLDER_PATH, coverageReportName) # reportLocally = os.path.join(self.runtimeReportsRootDir, coverageReportName) if not localReportFolderName: localReportFolderName="test" localReportFolder = self._createDir(self.runtimeReportsRootDir, localReportFolderName, True, False) self.bboxExecutor.stopOndeviceTesting(cancelAnalysis=False) time.sleep(3) #waiting for several seconds for report to be generated # success = self.bboxExecutor.getFileFromDevice(reportFileOnDevice, reportLocally) success = self.bboxExecutor.getFileFromDevice(self.deviceReportFolder, localReportFolder) if not success: self.bboxExecutor.removeFile(self.deviceReportFolder) return None if paramsToWrite: params_config = ConfigParser.ConfigParser() params_config.add_section(PARAMS_SECTION) for param in iteritems(paramsToWrite): params_config.set(PARAMS_SECTION, param[0], param[1]) with open(os.path.join(localReportFolder, "parameters.txt"), "w") as param_file: params_config.write(param_file) self.bboxExecutor.removeFile(self.deviceReportFolder) self._bboxStateMachine.transitToState(STATE_APK_FINISHED_TESTING) return localReportFolder def generateReport(self, reportFiles=[], reportName=None, reportType=EMMA_REPORT.XML): if not reportFiles: logger.error("No report files are provided!") return self.bboxReporter.cleanMetaFiles() self.bboxReporter.cleanReportFiles() self.bboxReporter.addMetaFile(self.coverageMetadataFile) for rFile in reportFiles: self.bboxReporter.addReportFile(rFile) reportsRoot = os.path.join(self.apkResultsDir, self.config.getReportsRelativeDir()) where = self._createReportResultsDir(reportsRoot, "report_%s" % reportType) self.bboxReporter.generateEmmaReport(where, reportName, reportType) def _createReportResultsDir(self, reportsRoot, reportDirName): i = 0 resultsDir = os.path.join(reportsRoot, reportDirName) while os.path.exists(resultsDir): i += 1 resultsDir = os.path.join(reportsRoot, "%s_%d" % (reportDirName, i)) auxiliary_utils.mkdir(path=resultsDir, mode=0777, overwrite=False) return resultsDir def uninstallPackage(self): self.bboxExecutor.uninstallPackage(packageName=self.packageName, keepData=False) self._bboxStateMachine.stop() @staticmethod def getCoverageReportsFromFolderWithPrefix(folder, prefix): if not os.path.exists(folder): return None reports = [] for file in os.listdir(folder): if file.endswith(".ec") and file.startswith(prefix): reports.append(os.path.join(folder, file)) return reports
def instrumentApkForCoverage(self, pathToOrigApk, resultsDir=None, tmpDir=None, removeApkTmpDirAfterInstr=True, copyApkToRes = True): ''' Args: :param pathToOrigApk: :param resultsDir: :param tmpDir: Returns: :ret True - if instrumentation was successful, False otherwise ''' self._bboxStateMachine.start(STATE_UNINITIALIZED) valid = self._checkProvidedApk(pathToOrigApk) if not valid: return False self._bboxStateMachine.transitToState(STATE_APK_VALID) resultsRootDir = None if not resultsDir: resultsRootDir = os.path.join(os.getcwd(), RESULTS_RELATIVE_DIR) else: resultsRootDir = os.path.abspath(resultsDir) tmpRootDir = None if not tmpDir: tmpRootDir = os.path.join(os.getcwd(), TMP_RELATIVE_DIR) else: tmpRootDir = os.path.abspath(tmpDir) apkFileName = os.path.splitext(os.path.basename(pathToOrigApk))[0] self.apkTmpDir = self._createDir(tmpDir, apkFileName, False, True) self.apkResultsDir = self._createDir(resultsRootDir, apkFileName, False, True) self.coverageMetadataFolder = self._createDir(self.apkResultsDir, self.config.getCoverageMetadataRelativeDir(), False, True) self.runtimeReportsRootDir = self._createDir(self.apkResultsDir, self.config.getRuntimeReportsRelativeDir(), False, True) self._bboxStateMachine.transitToState(STATE_FOLDERS_CREATED) #coping initial apk file if required if copyApkToRes: shutil.copy2(pathToOrigApk, self.apkResultsDir) #decompiling apk into a folder decompileDir = os.path.join(self.apkTmpDir, self.config.getDecompiledApkRelativeDir()) success = self._decompileApk(self.bboxInstrumenter, pathToOrigApk, decompileDir) if not success: return False self._bboxStateMachine.transitToState(STATE_APK_DECOMPILED) #getting all available dex files dexFilesRelativePaths = self._getDexFilePathsRelativeToDir(decompileDir) if not dexFilesRelativePaths: logger.error("There is no dex files to convert!") return False if "classes.dex" not in dexFilesRelativePaths: # "classes.dex" must be in the decompile root directory logger.error("There is no classes.dex file found in the list of dex files") return False #converting dex to jar files rawJarFilesRootDir = os.path.join(self.apkTmpDir, self.config.getTmpJarRelativeDir()) jarFilesRelativePaths = self._convertDex2JarFiles( converter=self.bboxInstrumenter, dexFilesRootDir=decompileDir, dexFilesRelativePaths=dexFilesRelativePaths, jarFilesRootDir=rawJarFilesRootDir, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_DEX_CONVERTED_TO_JAR) if "classes.jar" not in jarFilesRelativePaths: #main file is not converted logger.error("Conversion from classes.dex to classes.jar was not successful!") return False #instrumenting available jar files self.coverageMetadataFile = os.path.join(self.coverageMetadataFolder, self.config.getCoverageMetadataFilename()) emmaInstrJarFilesRootDir = os.path.join(self.apkTmpDir, self.config.getInstrumentedFilesRelativeDir()) emmaInstrJarFileRelativePaths = self._instrFilesWithEmma( instrumenter=self.bboxInstrumenter, jarFilesRootDir=rawJarFilesRootDir, jarFilesRelativePaths=jarFilesRelativePaths, instrJarsRootDir=emmaInstrJarFilesRootDir, coverageMetadataFile=self.coverageMetadataFile, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_JARS_INSTRUMENTED) if "classes.jar" not in emmaInstrJarFileRelativePaths: #main file is not instrumented logger.error("Instrumentation of classes.jar was not successful!") return False #converting jar files back to dex files instrDexFilesRelativePaths = self._convertJar2DexWithInstr( converter=self.bboxInstrumenter, instrJarsRootDir=emmaInstrJarFilesRootDir, instrJarFilesRelativePaths=emmaInstrJarFileRelativePaths, finalDexFilesRootDir=decompileDir, proceedOnError=True) self._bboxStateMachine.transitToState(STATE_JAR_CONVERTED_TO_DEX) if "classes.dex" not in instrDexFilesRelativePaths: logger.error("There is no classes.dex file found in the list of dex files") return False #checking what files have not been converted uninstrumentedFiles = self._getUnInstrFilesRelativePaths(dexFilesRelativePaths, instrDexFilesRelativePaths) if uninstrumentedFiles: logger.debug("The following files were not instrumented: %s" + str(uninstrumentedFiles)) #instrument AndroidManifest.xml decompiledAndroidManifestPath = os.path.join(decompileDir, "AndroidManifest.xml") success = self._instrAndroidManifest(self.bboxInstrumenter, decompiledAndroidManifestPath) if not success: logger.error("Cannot instrument AndroidManifest.xml file!") return False #coping instrumented AndroidManifest.xml to result folder shutil.copy2(decompiledAndroidManifestPath, self.apkResultsDir) self.androidManifestFile = os.path.join(self.apkResultsDir, "AndroidManifest.xml") self._bboxStateMachine.transitToState(STATE_MANIFEST_INSTRUMENTED) compiledApkFilePath = os.path.join(self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getInstrFileSuffix())) success = self._compileApk(self.bboxInstrumenter, decompileDir, compiledApkFilePath) if not success: logger.error("Cannot build apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_BUILD) #need to copy resources into file compiledApkFilePathWithEmmaRes = os.path.join(self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getFinalInstrFileSuffix())) shutil.copy2(compiledApkFilePath, compiledApkFilePathWithEmmaRes) self._putAdditionalResources(apk=compiledApkFilePathWithEmmaRes, resources=self.config.getEmmaResourcesDir()) self._bboxStateMachine.transitToState(STATE_FINAL_INSTRUMENTED_APK_BUILD) signedApkFilePath = os.path.join(self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getSignedFileSuffix())) success = self._signApk(self.bboxInstrumenter, compiledApkFilePathWithEmmaRes, signedApkFilePath) if not success: logger.error("Cannot sign apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_SIGNED) alignedApkFilePath = os.path.join(self.apkResultsDir, "%s%s.apk" % (apkFileName, self.config.getAlignedFileSuffix())) success = self._alignApk(self.bboxInstrumenter, signedApkFilePath, alignedApkFilePath) if not success: logger.error("Cannot align apk!") return False self._bboxStateMachine.transitToState(STATE_INSTRUMENTED_APK_ALIGNED) #cleaning: if tmp dir needs to be removed after instrumentation if removeApkTmpDirAfterInstr: shutil.rmtree(self.apkTmpDir) self.instrumentedApk = alignedApkFilePath self.androidManifest = AndroidManifest(self.androidManifestFile) self.packageName = self.androidManifest.getInstrumentationTargetPackage() self.runnerName = self.androidManifest.getInstrumentationRunnerName() #Final node transition self._bboxStateMachine.transitToState(STATE_APK_INSTRUMENTED) return True