Android Agent为完成Android端覆盖率报告的生成,关联用例等功能的服务,本来可以做成微服务的形式,早期由于和iOS端放在了一起,而iOS相关功能无法布置到容器上,就直接使用django开发了一个服务,通过Http接口完成相应的操作。后来功能越来越强大,就将Android和iOS拆分出来,Android agent为Flask框架开发的服务,django无法部署到公司的容器上。

6.2.1 整体架构

     目前Android agent的整体架构如下,随着精准测试平台功能的越来越多,Agent的功能也会不断地增加的。

功能介绍:

1,文件模块

      为Flask的一个接口模块,主要用来处理Agent对于文件的操作。比如下载项目代码,上传覆盖率文件,下载覆盖率报告,下载构建后的class文件,以及拷贝覆盖率文件等操作。用于精准测试平台与Agent交互的所有文件操作,其中的小技巧:如果文件在同一文件存储服务上,就采用拷贝的方法来代码上传操作,以提高执行速度。

(1)clone文件代码:

class CloneProFromGit(object):
    """
    从Git上下载代码
    """
    def cloneprofiles(self,gitadr,gitbran,propath):
        """下载或更新指定分支的代码"""
        utiloper=Utils()
        curpath = os.getcwd()
        print("开始Clone项目:"+gitadr)
        if not os.path.exists(propath+"XXXXXX"):
            os.makedirs(propath)
            # clone代码,需要优化一下
            if gitbran.find("master")!=-1:
                os.system("git clone "+gitadr+" "+propath)
                print("Clone代码库,master分支......")
            else:
                # 切换分支,可能超时
                os.system("git clone "+gitadr+" "+propath)
                os.chdir(propath)
                os.system("git checkout -b "+gitbran+" remotes/origin/"+gitbran)
                os.system("git pull")
                print("Clone代码库,并更新分支......")
        else:
            #更新代码库
            os.chdir(propath)
            os.system("git checkout -b "+gitbran+" remotes/origin/"+gitbran)
            os.system("git pull")
            print("Clone代码库,代码库已经存在,切换分支:"+gitbran)
        return propath

2,覆盖率模块

      通过上传构建后的class文件,使用jacococli工具来生成指定的需求的全量覆盖率报告,这样可以防止通过项目构建生成Class文件来生成报告。同时对于增量覆盖率报告有两种处理情况:

(1)对比分支的增量报告,利用开源工具diff-cover来生成增量报告,而后再生成一个汇总页面;核心代码:

# coding=utf-8
import logging
from pathlib import Path
import asyncio, os
import time, sys, shutil

logger=logging.getLogger("AndroidDiffCoverReportOper")

class AndroidDiffCoverReportOper(object):
    """
    多线程执行生成diff-cover覆盖率报告,以提高执行速度
    @author SXF
    @date 2022-05-27
    """

    def getReportHeader(self, branch):
        """生成增量覆盖率报告"""
        headcontent = '''<?xml version="1.0" encoding="UTF-8"?>
            <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
            <html xmlns="http://www.w3.org/1999/xhtml" lang="zh">
            <head>
                    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
                    <link rel="stylesheet" href="resources/report.css" type="text/css"/>
                    <title>Diff Coverage</title>
            </head>
            <body>
                <h1>Diff Coverage</h1>
            '''
        headcontent = headcontent + "<p>Diff: " + branch + "...HEAD, staged and unstaged changes</p>\r\n"
        return headcontent

    def createTotalReport(self, propath, branch, srcfold):
        """
        生成最终的汇总报告
        """
        reportcontent = "<table class=\"coverage\" cellspacing=\"0\" id=\"coveragetable\">\r\n<thead><tr><th>Pagckages</th><th>Diff Coverage (%) </th><th width=\"100\">Total Lines </th><th width=\"100\">Missing Lines </th></tr></thead>\r\n"
        totallines = 0
        mislines = 0
        index = 0
        for sfold in srcfold:
            reportcontent = reportcontent + "<tr><td><a href=\"diffreport" + str(
                index) + ".html\" class=\"el_package\">" + sfold[
                                                           sfold.find("packages"):sfold.find("src") - 1] + "</a>  </td>"
            diffreport = propath + "diffreport/diffreport" + str(index) + ".html"
            dfile = open(diffreport, "r")
            lines = dfile.readlines()
            if len(lines) < 94:
                # diff-cover生成的报告中无数据
                reportcontent = reportcontent + "<td colspan='3' class=\"ctr1\">No lines with coverage information in this diff.</td>"
            else:
                cktotal = ""
                ckmisline = ""
                ckcovrate = 0
                flag = 0
                for line in lines:
                    if line.find("<b>Total</b>") > -1:
                        cktotal = line[line.find(":") + 1:line.find("line")]
                        totallines = totallines + int(cktotal.strip())
                        flag = flag + 1
                    if line.find("<b>Missing</b>") > -1:
                        ckmisline = line[line.find(":") + 1:line.find("line")]
                        mislines = mislines + int(ckmisline.strip())
                        flag = flag + 1
                    if line.find("<b>Coverage</b>") > -1:
                        ckcovrate = int(line[line.find(":") + 1:line.find("%")].strip())
                        flag = flag + 1
                # 统一添加相关数据
                if flag == 3:
                    # 1,覆盖率
                    miscov = 100 - ckcovrate
                    reportcontent = reportcontent + "<td class=\"bar\">" + "<img src=\"resources/redbar.gif\" width=\"" + str(
                        miscov) + "\" height=\"10\">"
                    reportcontent = reportcontent + "<img src=\"resources/greenbar.gif\" width=\"" + str(
                        ckcovrate) + "\" height=\"10\">  " + str(ckcovrate) + "%</td>"
                    # 2,总行数
                    reportcontent = reportcontent + "<td class=\"ctr1\">" + cktotal + "</td>"
                    # 3,没有覆盖的行数
                    reportcontent = reportcontent + "<td class=\"ctr1\">" + ckmisline + "</td>"
            reportcontent = reportcontent + "</tr>\r\n"
            index = index + 1
        reportcontent = reportcontent + "</table>"
        # 添加头部信息及汇总信息
        covlines = totallines - mislines
        if totallines == 0:
            covrate=0
        else:
            covrate = float(covlines) / float(totallines) * 100
        headcontent = self.getReportHeader(branch)
        headcontent = headcontent + "<ul>\r\n<li><b>Total</b>: " + str(totallines) + " lines</li>\r\n"
        headcontent = headcontent + "<li><b>Missing</b>: " + str(mislines) + " lines</li>\r\n"
        headcontent = headcontent + "<li><b>Coverage</b>: " + str(round(covrate, 2)) + " %</li>\r\n</ul>\r\n"
        reportcontent = headcontent + reportcontent + "\r\n</body>\r\n</html>"
        # 汇总报告写入文件
        difffolder=propath + "diffreport"
        if not os.path.exists(difffolder):
            os.makedirs(difffolder)
        findreport = difffolder+"/index.html"
        rf = open(findreport, "w")
        rf.write(reportcontent)
        rf.close
        logger.info("生成汇总报告:" + findreport)

    def getDiffFile(self, propath,branch):
        """获取Diff文件"""
        curpath = os.getcwd()
        logger.info("getDiffFile中的当前路径:"+curpath)
        os.chdir(propath)
        gitcmd = "git diff " + branch + "|grep 'diff --git'"
        runcmd = os.popen(gitcmd)
        fileinfo = runcmd.readlines()
        pclist = []
        for line in fileinfo:
            if (line.find(".kt") > -1 or line.find(".java") > -1):
                if (line.find(".kt") > -1):
                    getfile = line[line.find("a/") + 2:line.find(".kt") + 3]
                else:
                    getfile = line[line.find("a/") + 2:line.find(".java") + 5]
                packagepath = getfile[9:getfile.find("/src")]
                if not (packagepath in pclist):
                    pclist.append(packagepath)
        # print(pclist)
        os.chdir(curpath)
        return pclist

    def getAllSourcePath(self, propath, branch):
        """
        获取项目中的所有源码路径
        :param propath:
        :return:
        """
        sroucepath = []
        diffpclist = self.getDiffFile(propath,branch)
        folder = Path(propath)
        result = list(folder.rglob("src/main/java"))
        for ckfile in result:
            ckpath = str(ckfile.resolve())
            for packinfo in diffpclist:
                if ckpath.find(packinfo) > -1:
                    sroucepath.append(ckpath)
                    break
        # print (sroucepath)
        return sroucepath

    async def creatDiffReport(self, propath, combran, srcpath, index):
        """
        生成增量覆盖率报告
        """
        logger.info("开始生成" + srcpath + "的增量报告.........")
        jacocoxml = propath + "jacocoTestReport.xml"
        diffrepfold = propath + "diffreport"
        if not os.path.exists(diffrepfold):
            os.makedirs(diffrepfold)
        os.chdir(propath)
        diffcmd = "diff-cover " + jacocoxml + " --html-report " + diffrepfold + "/diffreport" + str(
            index) + ".html --compare-branch " + combran + " --src-roots " + srcpath
        proc = await asyncio.create_subprocess_shell(diffcmd)
        stdout, stderr = await proc.communicate()
        return srcpath + "的增量报告生成完成!"

    async def runDiffCreateTask(self, propath, combran, srclist, start):
        """
        批量生成报告任务
        """
        task_list = []
        index = start
        loop=asyncio.get_event_loop()
        for srcpath in srclist:
            task = loop.create_task(self.creatDiffReport(propath, combran, srcpath, index))
            task_list.append(task)
            index = index + 1
        done, pending = await asyncio.wait(task_list, timeout=None)
        for done_task in done:
            print(done_task.result())

    def createDiffCoverReportOfAndroid(self, propath, branch):
        """
        生成Android的增量报告
        """
        logger.info("*******************开始生成增量报告*********************")
        print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
        srcfold = self.getAllSourcePath(propath, branch)
        curpath = os.getcwd()
        index = 0
        for i in range(len(srcfold)):
            # 取子列表,一次并发10个
            if index >= len(srcfold):
                break
            sublist = srcfold[index:index + 10]
            loop = asyncio.get_event_loop()
            loop.run_until_complete(self.runDiffCreateTask(propath, branch, sublist, index))
            index = index + 10
        logger.info("所有增量报告生成完成.....")
        # 生成汇总报告
        self.createTotalReport(propath, branch, srcfold)
        logger.info("*******************结束生成增量报告*********************")
        #将diffreport打包
        os.chdir(propath)
        shutil.make_archive("diffreport","zip",'./diffreport')
        logger.info("将diffreport打包!")
        os.chdir(curpath)
        print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))


if __name__ == '__main__':
    propath = sys.argv[1]
    combranch = sys.argv[2]
    crfk = AndroidDiffCoverReportOper()
    crfk.createDiffCoverReportOfAndroid(propath, combranch)
 

(2)对比版本的增量报告,由于diff-cover不支持版本对比,就单独开发了版本对比的功能。通过git diff来获取变更的文件列表,再去过滤全量覆盖率报告,最终生成增量覆盖率报告。核心代码:

# coding=utf-8
import os
import shutil
from datetime import time
from pathlib import Path
from flask import current_app

from AndroidCovAgent.CovRelateOperation.JacocoReportSelectOper import JacocoReportSelectOper


class AndroidComPareCommitsReport(object):
    """
    生成Android版本对比覆盖率报告
    """
    def selectReportByDiffFiles(self,filelist,jacocorpath,diffreport):
        """
        根据diff文件列表,生成相应的增量覆盖率报告文件
        :param filelist:
        :param jacocorpath:
        :return:
        """
        for ckfile in filelist:
            #解析文件名
            ckfname=ckfile[ckfile.rindex("/")+1:ckfile.rindex(".")]
            changepath=ckfile[ckfile.index("com"):ckfile.rindex("/")].replace("/",".")
            folder=Path(jacocorpath)
            result=list(folder.rglob(changepath+"/"+ckfname+"*"))
            for ckfile in result:
                ckpath=str(ckfile.resolve())
                newpath=diffreport+ckpath[ckpath.index("jacoco")+7:ckpath.rindex("/")]
                newfilename=ckpath[ckpath.rindex("/")+1:]
                if not os.path.exists(newpath):
                    os.makedirs(newpath)
                    #拷贝index.html文件
                    shutil.copy(ckpath[0:ckpath.rindex("/")+1]+"index.html",newpath+"/index.html")
                shutil.copy(ckpath,newpath+"/"+newfilename)
                # print(ckpath)

    def getDiffFile(self, propath,comparecommit):
        """获取Diff文件"""
        curpath = os.getcwd()
        os.chdir(propath)
        gitcmd = "git diff " + comparecommit + "|grep 'diff --git'"
        runcmd = os.popen(gitcmd)
        fileinfo = runcmd.readlines()
        # print(fileinfo)
        difffiles = []
        for line in fileinfo:
            if (line.find(".kt") > -1 or line.find(".java") > -1):
                if (line.find(".kt") > -1):
                    getfile = line[line.find("a/") + 2:line.find(".kt") + 3]
                else:
                    getfile = line[line.find("a/") + 2:line.find(".java") + 5]
                if not (getfile in difffiles):
                    difffiles.append(getfile)
        # print(pclist)
        os.chdir(curpath)
        return difffiles


    def createDiffReportByCompareCommits(self,propath,comparecomit):
        """
        对比分支,生成增量覆盖率报告
        :param propath:
        :param comparecomit:
        :return:
        """
        #获取diff文件列表
        difffilelist=self.getDiffFile(propath,comparecomit)
        #print(difffilelist)
        jacocopath=propath+"jacoco/"
        diffrppath=propath+"diffreport/"
        if not os.path.exists(diffrppath):
            os.makedirs(diffrppath)
        #拷贝资源文件到diffreport文件夹中
        shutil.copytree(jacocopath+"/jacoco-resources",diffrppath+"/jacoco-resources")
        print("创建增量覆盖率文件夹,拷贝资源文件!")
        #拷贝增量覆盖率文件相关报告
        self.selectReportByDiffFiles(difffilelist,jacocopath,diffrppath)
        #修改各个package下的index文件,去掉不是diff中的文件信息
        jrsoper=JacocoReportSelectOper()
        for i,j,k in os.walk(diffrppath):
            for folder in j:
                # print("package="+folder)
                if folder.find("jacoco-resources")==-1:
                    packagepath=diffrppath+folder+"/"
                    jrsoper.correctIndexFile(packagepath)
        #生成最终的覆盖率报告
        jrsoper.createDiffReport(diffrppath,comparecomit)
        print("对比版本:"+comparecomit+"的增量报告生成完成!")
        #将全量覆盖率报告打包
        curpath=os.getcwd()
        os.chdir(propath)
        shutil.make_archive("jacoco","zip",'./jacoco')
        #将增量报告打包
        shutil.make_archive("diffreport","zip",'./diffreport')
        os.chdir(curpath)

if __name__=='__main__':
    acpcr=AndroidComPareCommitsReport()
    propath="/Users/*******/"
acpcr.createDiffReportByCompareCommits(propath,"321ce030db507b72f04da3fa09b9e90e3be7313e")

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐