为什么 Sonar 不读取 django-nose 覆盖结果的格式?

Why doesn't Sonar read the format of django-nose coverage results?

当我尝试使用 sonar-scanner 发送 python 项目时,它抛出异常:

Caused by: java.lang.IllegalStateException: Unknown report version: 4.4.2. 
This parser only handles version 1.". 

在尝试介绍封面之前,项目通常是运行,项目是python代码,报告是用django-nose创建的。

更多信息:

我附上了 django-nose 生成的声纳项目配置文件和覆盖率 xml 文件。

异常:

12:53:31.919 ERROR: Error during SonarQube Scanner execution
Error during parsing of the generic coverage report '/home/bamboo-home/xml-data/build-dir/NAN-CI6-JOB1/xmlrunner/coverage.xml'. Look at SonarQube documentation to know the expected XML format.
Caused by: java.lang.IllegalStateException: Unknown report version: 4.4.2. This parser only handles version 1.
    at org.sonar.scanner.genericcoverage.GenericCoverageReportParser.parseRootNode(GenericCoverageReportParser.java:72)
    at org.sonar.scanner.genericcoverage.GenericCoverageReportParser.lambda$parse[=12=](GenericCoverageReportParser.java:64)
    at org.sonar.api.utils.StaxParser.parse(StaxParser.java:115)
    at org.sonar.api.utils.StaxParser.parse(StaxParser.java:95)
    at org.sonar.scanner.genericcoverage.GenericCoverageReportParser.parse(GenericCoverageReportParser.java:65)
    at org.sonar.scanner.genericcoverage.GenericCoverageReportParser.parse(GenericCoverageReportParser.java:54)
    at org.sonar.scanner.genericcoverage.GenericCoverageSensor.execute(GenericCoverageSensor.java:109)
    at org.sonar.scanner.sensor.SensorWrapper.analyse(SensorWrapper.java:53)
    at org.sonar.scanner.phases.SensorsExecutor.executeSensor(SensorsExecutor.java:88)
    at org.sonar.scanner.phases.SensorsExecutor.execute(SensorsExecutor.java:82)
    at org.sonar.scanner.phases.SensorsExecutor.execute(SensorsExecutor.java:68)
    at org.sonar.scanner.phases.AbstractPhaseExecutor.execute(AbstractPhaseExecutor.java:88)
    at org.sonar.scanner.scan.ModuleScanContainer.doAfterStart(ModuleScanContainer.java:180)
    at org.sonar.core.platform.ComponentContainer.startComponents(ComponentContainer.java:135)
    at org.sonar.core.platform.ComponentContainer.execute(ComponentContainer.java:121)
    at org.sonar.scanner.scan.ProjectScanContainer.scan(ProjectScanContainer.java:288)
    at org.sonar.scanner.scan.ProjectScanContainer.scanRecursively(ProjectScanContainer.java:283)
    at org.sonar.scanner.scan.ProjectScanContainer.doAfterStart(ProjectScanContainer.java:261)
    at org.sonar.core.platform.ComponentContainer.startComponents(ComponentContainer.java:135)
    at org.sonar.core.platform.ComponentContainer.execute(ComponentContainer.java:121)
    at org.sonar.scanner.task.ScanTask.execute(ScanTask.java:48)
    at org.sonar.scanner.task.TaskContainer.doAfterStart(TaskContainer.java:84)
    at org.sonar.core.platform.ComponentContainer.startComponents(ComponentContainer.java:135)
    at org.sonar.core.platform.ComponentContainer.execute(ComponentContainer.java:121)
    at org.sonar.scanner.bootstrap.GlobalContainer.executeTask(GlobalContainer.java:121)
    at org.sonar.batch.bootstrapper.Batch.doExecuteTask(Batch.java:116)
    at org.sonar.batch.bootstrapper.Batch.executeTask(Batch.java:111)
    at org.sonarsource.scanner.api.internal.batch.BatchIsolatedLauncher.execute(BatchIsolatedLauncher.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.sonarsource.scanner.api.internal.IsolatedLauncherProxy.invoke(IsolatedLauncherProxy.java:60)
    at com.sun.proxy.$Proxy0.execute(Unknown Source)
    at org.sonarsource.scanner.api.EmbeddedScanner.doExecute(EmbeddedScanner.java:233)
    at org.sonarsource.scanner.api.EmbeddedScanner.runAnalysis(EmbeddedScanner.java:151)
    at org.sonarsource.scanner.cli.Main.runAnalysis(Main.java:123)
    at org.sonarsource.scanner.cli.Main.execute(Main.java:77)
    at org.sonarsource.scanner.cli.Main.main(Main.java:61)

配置:

sonar.projectKey=nanas
sonar.projectName=Nanas
sonar.projectVersion=1.0
sonar.sourceEncoding=UTF-8
sonar.sources=nucleo, app_administrador, app_cliente, app_gestor_de_citas, app_nana, app_test, django_gae_emailbackend
sonar.exclusions=**/tests/**, **/static/**/template/**, **/migrations/**, **/__pycache__/**, **/__init__.*
sonar.tests=xmlrunner/nosetests.xml
sonar.genericcoverage.reportPaths=xmlrunner/coverage.xml
sonar.python.coveragePlugin=cobertura
sonar.coverage.dtdVerification=false

Coverage.xml header:

<?xml version="1.0" ?>
<coverage branch-rate="0" branches-covered="0" branches-valid="0" complexity="0" line-rate="0.5905" lines-covered="1142" lines-valid="1934" timestamp="1516639621366" version="4.4.2">
    <!-- Generated by coverage.py: https://coverage.readthedocs.io -->
    <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
    <sources>

我已经根据指示配置了扫描仪,并将插件更新到 1.9 版本,但现在它会生成行数错误。我在几个网站上搜索了解决方案,几乎一切都归结为添加一个空行,但这不起作用。

运行 测试:

nosetests --with-coverage --cover-package=app_administrador,app_cliente,app_nana,app_test,app_gestor_de_citas,nucleo --cover-erase --cover-xml --with-xunit --xunit-file=xmlrunner/nosetests.xml --cover-xml-file=xmlrunner/coverage.xml --attr=!pendiente --verbosity=1

运行声纳:

sonar-scanner   -Dsonar.projectKey=nana   -Dsonar.host.url=http://192.168.0.15:9001   -Dsonar.login=*********

sonar-project.properties

sonar.projectKey=nanas
sonar.projectName=nanas
sonar.projectVersion=1.0
sonar.sourceEncoding=UTF-8
sonar.sources=nucleo, app_administrador, app_cliente, app_gestor_de_citas, app_nana, app_test, django_gae_emailbackend
sonar.exclusions=**/tests/**, **/static/**/template/**, **/migrations/**, **/__pycache__/**, **/admin.py
sonar.tests=xmlrunner/nosetests.xml
sonar.python.coverage.reportPath=xmlrunner/coverage.xml

错误:

ERROR: Error during SonarQube Scanner execution
java.lang.IllegalStateException: Line 21 is out of range in the file app_administrador/middleware.py (lines: 19)
        at org.sonar.api.internal.google.common.base.Preconditions.checkState(Preconditions.java:197)

Python插件

SonarPython 1.9 (build 1989)installed

SonarPython 具有对 coverage.py 报告的嵌入式支持。 要启用它,您可以替换您的通用覆盖配置:

sonar.genericcoverage.reportPaths=xmlrunner/coverage.xml
sonar.python.coveragePlugin=cobertura
sonar.coverage.dtdVerification=false

通过具体的SonarPython配置:

sonar.python.coverage.reportPath=xmlrunner/coverage.xml

有关详细信息,请参阅 Python Coverage Results Import

注意:SonarPython 1.9 已对 coverage.py 进行了改进(最高 4.4.2),如果您还不能下载 SonarPython 1.9,则候选版本为 here.

我终于知道是怎么回事了。扫描器似乎混淆了不同 django 应用程序中的两个同名文件,因为每个应用程序都在 django-nose 的 django 设置中配置为 "cover-package"。

显然 django-nose 将所有数据包根文件视为单个模块“./”,如果有重复文件(例如:admin.py)只读取一个,但是当您将数据传递给 python 扫描仪,它会尝试读取另一个扫描仪,如果它们都有不同的行,则会引发冲突,我想如果它们碰巧有相同的行,声纳覆盖代码与行的表示也将是错了。

更改前django-nose生成的覆盖文件:

  <?xml version="1.0" ?>
<coverage branch-rate="0" branches-covered="0" branches-valid="0" complexity="0" line-rate="0.5905" lines-covered="1142" lines-valid="1934" timestamp="1516895775536" version="4.4.2">
    <!-- Generated by coverage.py: https://coverage.readthedocs.io -->
    <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
    <sources>
        <source>/home/jvallina/PycharmProjects/nana_project/app_administrador</source>
        <source>/home/jvallina/PycharmProjects/nana_project/app_cliente</source>
        <source>/home/jvallina/PycharmProjects/nana_project/app_gestor_de_citas</source>
        <source>/home/jvallina/PycharmProjects/nana_project/app_nana</source>
        <source>/home/jvallina/PycharmProjects/nana_project/app_test</source>
        <source>/home/jvallina/PycharmProjects/nana_project/nucleo</source>
    </sources>
    <packages>
        <package branch-rate="0" complexity="0" line-rate="0.5668" name=".">
            <classes>
                <class branch-rate="0" complexity="0" filename="__init__.py" line-rate="1" name="__init__.py">
                    <methods/>
                    <lines/>
                </class>
                <class branch-rate="0" complexity="0" filename="admin.py" line-rate="0" name="admin.py">
                    <methods/>
                    <lines>
                        <line hits="0" number="1"/>
                        ...
                        <line hits="0" number="238"/>
                    </lines>
                </class>

然后我们要替换它(django设置):

NOSE_ARGS = [
    '--with-coverage',
    '--cover-package=app_administrador,app_cliente,app_nana,app_test,app_gestor_de_citas,nucleo',
    '--cover-erase',
    '--cover-xml',
    '--with-xunit',
    '--xunit-file=xmlrunner/nosetests.xml',
    '--cover-xml-file=xmlrunner/coverage.xml'
]

这样:

NOSE_ARGS = [
    '--with-coverage',
    '--cover-package=.',
    '--cover-erase',
    '--cover-xml',
    '--with-xunit',
    '--xunit-file=xmlrunner/nosetests.xml',
    '--cover-xml-file=xmlrunner/coverage.xml'
]

修改后的覆盖文件: 我们可以看到模块中重复的每个文件都已经通过其源应用程序进行了区分。

 <?xml version="1.0" ?>
    <coverage branch-rate="0" branches-covered="0" branches-valid="0" complexity="0" line-rate="0.6718" lines-covered="3069" lines-valid="4568" timestamp="1516896635767" version="4.4.2">
        <!-- Generated by coverage.py: https://coverage.readthedocs.io -->
        <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
        <sources>
            <source>/home/jvallina/PycharmProjects/nana_project</source>
        </sources>
        <packages>
            <package branch-rate="0" complexity="0" line-rate="0" name=".">
                <classes>
                    <class branch-rate="0" complexity="0" filename="manage.py" line-rate="0" name="manage.py">
                        <methods/>
                        <lines>
                            <line hits="0" number="2"/>
                            <line hits="0" number="3"/>
                            <line hits="0" number="5"/>
                            <line hits="0" number="6"/>
                            <line hits="0" number="7"/>
                            <line hits="0" number="8"/>
                            <line hits="0" number="9"/>
                            <line hits="0" number="13"/>
                            <line hits="0" number="14"/>
                            <line hits="0" number="15"/>
                            <line hits="0" number="16"/>
                            <line hits="0" number="21"/>
                            <line hits="0" number="22"/>
                        </lines>
                    </class>
                </classes>
            </package>
            <package branch-rate="0" complexity="0" line-rate="0.4884" name="app_administrador">
    <classes>
                    <class branch-rate="0" complexity="0" filename="app_administrador/__init__.py" line-rate="1" name="__init__.py">
                        <methods/>
                        <lines/>
                    </class>
                    <class branch-rate="0" complexity="0" filename="app_administrador/admin.py" line-rate="1" name="admin.py">
                        <methods/>
                        <lines/>
                    </class>
    ...
    </package>
<package branch-rate="0" complexity="0" line-rate="0.7931" name="app_cliente">
            <classes>
                <class branch-rate="0" complexity="0" filename="app_cliente/__init__.py" line-rate="1" name="__init__.py">
                    <methods/>
                    <lines/>
                </class>
                <class branch-rate="0" complexity="0" filename="app_cliente/admin.py" line-rate="1" name="admin.py">
                    <methods/>
                    <lines/>
                </class>
...
</package>