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)
Пример #2
0
 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)
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
Пример #4
0
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