├── .travis.yml ├── LICENSE.txt ├── README.md ├── doc └── img │ ├── 01.png │ ├── 02.png │ ├── 03.png │ └── 04.png ├── parent └── pom.xml ├── plugin ├── .gitignore ├── README.md ├── dev.sh ├── pom.xml └── src │ ├── main │ ├── resources │ │ └── com │ │ │ └── buransky │ │ │ └── plugins │ │ │ └── scoverage │ │ │ └── widget.html.erb │ └── scala │ │ └── com │ │ └── buransky │ │ └── plugins │ │ └── scoverage │ │ ├── ScoverageExtensionProvider.scala │ │ ├── ScoveragePlugin.scala │ │ ├── ScoverageReportParser.scala │ │ ├── StatementCoverage.scala │ │ ├── language │ │ └── Scala.scala │ │ ├── measure │ │ └── ScalaMetrics.scala │ │ ├── pathcleaner │ │ ├── BruteForceSequenceMatcher.scala │ │ └── PathSanitizer.scala │ │ ├── sensor │ │ └── ScoverageSensor.scala │ │ ├── util │ │ ├── LogUtil.scala │ │ └── PathUtil.scala │ │ ├── widget │ │ └── ScoverageWidget.scala │ │ └── xml │ │ ├── XmlScoverageReportConstructingParser.scala │ │ └── XmlScoverageReportParser.scala │ └── test │ └── scala │ └── com │ └── buransky │ └── plugins │ └── scoverage │ ├── pathcleaner │ └── BruteForceSequenceMatcherSpec.scala │ ├── sensor │ ├── ScoverageSensorSpec.scala │ └── TestSensorContext.scala │ ├── util │ └── PathUtilSpec.scala │ └── xml │ ├── XmlScoverageReportConstructingParserSpec.scala │ ├── XmlScoverageReportParserSpec.scala │ └── data │ └── XmlReportFile1.scala └── samples ├── maven ├── .gitignore ├── README ├── combined-scala-java-multi-module-sonar │ ├── module1 │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ ├── java │ │ │ │ └── module1 │ │ │ │ │ └── HelloWorld.java │ │ │ └── scala │ │ │ │ └── module1 │ │ │ │ └── HelloScala.scala │ │ │ └── test │ │ │ ├── java │ │ │ └── module1 │ │ │ │ └── HelloWorldTest.java │ │ │ └── scala │ │ │ └── HelloScalaTest.scala │ ├── module2 │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ ├── java │ │ │ │ └── module2 │ │ │ │ │ └── HelloWorld2.java │ │ │ └── scala │ │ │ │ └── module2 │ │ │ │ └── HelloScala2.scala │ │ │ └── test │ │ │ ├── java │ │ │ └── HelloWorld2Test.java │ │ │ └── scala │ │ │ └── HelloScala2Test.scala │ └── pom.xml └── combined-scala-java-sonar │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── module1 │ │ │ └── HelloWorld.java │ └── scala │ │ └── module1 │ │ └── HelloScala.scala │ └── test │ ├── java │ └── module1 │ │ └── HelloWorldTest.java │ └── scala │ └── HelloScalaTest.scala └── sbt └── multi-module ├── .gitignore ├── README.md ├── build.sbt ├── module1 ├── build.sbt └── src │ ├── main │ └── scala │ │ └── com │ │ └── buransky │ │ └── plugins │ │ └── scoverage │ │ └── samples │ │ └── sbt │ │ └── multiModule │ │ └── module1 │ │ ├── Beer.scala │ │ └── Pub.scala │ └── test │ └── scala │ └── com │ └── buransky │ └── plugins │ └── scoverage │ └── samples │ └── sbt │ └── multiModule │ └── module1 │ ├── BeerSpec.scala │ └── PubSpec.scala ├── module2 ├── build.sbt └── src │ ├── main │ └── scala │ │ └── com │ │ └── buransky │ │ └── plugins │ │ └── scoverage │ │ └── samples │ │ └── sbt │ │ └── multiModule │ │ └── module2 │ │ └── Animal.scala │ └── test │ └── scala │ └── com │ └── buransky │ └── plugins │ └── scoverage │ └── samples │ └── sbt │ └── multiModule │ └── module2 │ └── AnimalSpec.scala ├── project ├── Common.scala ├── build.properties └── plugins.sbt └── sonar-project.properties /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | script: "mvn -f plugin/pom.xml install" 3 | jdk: 4 | - openjdk7 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Scoverage Plugin for Sonar# 2 | 3 | [![Build Status](https://travis-ci.org/RadoBuransky/sonar-scoverage-plugin.png)](https://travis-ci.org/RadoBuransky/sonar-scoverage-plugin) 4 | [![License](https://img.shields.io/badge/license-LGPL-orange.svg)](http://www.gnu.org/licenses/lgpl.txt) 5 | [![Analytics](https://ga-beacon.appspot.com/UA-55603212-2/sonar-scoverage-plugin)](https://github.com/igrigorik/ga-beacon) 6 | 7 | Plugin for [SonarQube] that imports statement coverage generated by [Scoverage] for Scala projects. 8 | 9 | Scoverage measures how many statements of a given Scala program have been covered by automated tests. This 10 | new metric is much better for Scala than traditional line coverage and branch coverage because typically: 11 | 12 | 1. There are many statements on a single line 13 | 2. `if` statements are used rarely 14 | 15 | This plugin reads XML report generated by Scoverage and populates several metrics in Sonar: 16 | 17 | 1. Total number of statements 18 | 2. Number of statements covered by tests 19 | 3. Statement coverage rate (%) 20 | 21 | Projects with sub-projects are supported as well. Overall statement coverage is (sum of number of covered statements 22 | for all sub-projects) / (total number of statements for all sub-projects). In other words, it's more intelligent than 23 | just plain average of coverage rates for sub-projects. 24 | 25 | ## Requirements ## 26 | 27 | - [SonarQube] 5.1 28 | - [Scoverage] 1.1.0 29 | 30 | ### Support for older versions of Sonar ### 31 | 32 | - [SonarQube 4.5] (https://github.com/RadoBuransky/sonar-scoverage-plugin/tree/sonar45) 33 | - [SonarQube 4.2] (https://github.com/RadoBuransky/sonar-scoverage-plugin/tree/sonar42) 34 | - [SonarQube 3.5] (https://github.com/RadoBuransky/sonar-scoverage-plugin/tree/sonar35) 35 | 36 | ## Installation ## 37 | 38 | Download and copy [the plugin jar](https://github.com/RadoBuransky/sonar-scoverage-plugin/releases) to the Sonar plugins directory 39 | (usually /extensions/plugins). Restart Sonar. 40 | 41 | ## Configure Sonar runner ## 42 | 43 | Set location of the **scoverage.xml** file in the **sonar-project.properties** located in your project's 44 | root directory: 45 | 46 | ... 47 | sonar.scoverage.reportPath=target/scala-2.11/scoverage-report/scoverage.xml 48 | ... 49 | 50 | ## Run Scoverage and Sonar runner ## 51 | 52 | If your project is based on SBT and you're using [Scoverage plugin for SBT] [sbt-scoverage] you can 53 | generate the Scoverage report by executing following from command line: 54 | 55 | $ sbt clean coverage test 56 | 57 | And then run Sonar runner to upload the report to the Sonar server: 58 | 59 | $ sonar-runner 60 | 61 | ## Add statement coverage columns ## 62 | 63 | To see the actual statement coverage percentage you need to log in to Sonar as admin. 64 | Click **Components** section on the left side, then click **Customize ON** in the top-right corner and then 65 | add **Statement coverage** column. 66 | 67 | ## Add widget ## 68 | 69 | You can also add **Statement coverage widget** to your project's dashboard. Log in to Sonar as admin. Go to 70 | the project dashboard, click **Configure widgets** in the top-right corner, click **Add widget** button in 71 | the **Custom Measures** section. Click **Edit** in the newly added **Custom Measures** widget and choose 72 | **Statement coverage** for **Metric 1**. Click **Save**, **Back to dashboard**. Enjoy. 73 | 74 | ## Sample project ## 75 | 76 | Take a look at a sample SBT multi-module project located in this repository in the `samples` folder. 77 | 78 | ## Screenshots ## 79 | 80 | Project dashboard with Scoverage plugin: 81 | ![Project dashboard with Scoverage plugin](/doc/img/01.png "Project dashboard with Scoverage plugin") 82 | 83 | Multi-module project overview: 84 | ![Multi-module project overview](/doc/img/02.png "Multi-module project overview") 85 | 86 | Columns with statement coverage, total number of statements and number of covered statements: 87 | ![Columns](/doc/img/03.png "Columns") 88 | 89 | Source code markup with covered and uncovered lines: 90 | ![Source code markup](/doc/img/04.png "Source code markup") 91 | 92 | ## Changelog ## 93 | 94 | ### 5.1.3 - 8 April 2016 ### 95 | 96 | - Fixed [issue #31](https://github.com/RadoBuransky/sonar-scoverage-plugin/issues/31) 97 | 98 | ### 5.1.2 - 25 October 2015 ### 99 | 100 | **[Michael Zinsmaier](https://github.com/MichaelZinsmaier) pull requests:** 101 | 102 | - [Improved path handling, reported filenames are converted to src dir relative paths](https://github.com/RadoBuransky/sonar-scoverage-plugin/pull/22) 103 | - [Adding directory coverage thus supporting the Treemap widget](https://github.com/RadoBuransky/sonar-scoverage-plugin/pull/23) 104 | - [Added total statements metric to avoid overlaps with coremetrics](https://github.com/RadoBuransky/sonar-scoverage-plugin/pull/24) 105 | 106 | **[Justin Kaeser](https://github.com/jastice) pull request:** 107 | 108 | - [fix link syntax, link to releases](https://github.com/RadoBuransky/sonar-scoverage-plugin/pull/26) 109 | 110 | ### 5.1.1 - 7 May 2015 ### 111 | 112 | - Upgrade to SonarQube 5.1 API 113 | 114 | ### 1.1.0 - 23 Sep 2014 ### 115 | 116 | - Upgrade to SonarQube 4.2 API 117 | 118 | [LatestPluginJar]: https://github.com/RadoBuransky/sonar-scoverage-plugin/releases/download/v5.1.1/sonar-scoverage-plugin-5.1.1.jar 119 | [SonarQube]: http://www.sonarqube.org/ "SonarQube" 120 | [Scoverage]: https://github.com/scoverage/scalac-scoverage-plugin "Scoverage" 121 | [sbt-scoverage]: https://github.com/scoverage/sbt-scoverage 122 | -------------------------------------------------------------------------------- /doc/img/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadoBuransky/sonar-scoverage-plugin/e424f2cf8cbe5c3fdf8a6bee22eb97ab9de8ea71/doc/img/01.png -------------------------------------------------------------------------------- /doc/img/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadoBuransky/sonar-scoverage-plugin/e424f2cf8cbe5c3fdf8a6bee22eb97ab9de8ea71/doc/img/02.png -------------------------------------------------------------------------------- /doc/img/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadoBuransky/sonar-scoverage-plugin/e424f2cf8cbe5c3fdf8a6bee22eb97ab9de8ea71/doc/img/03.png -------------------------------------------------------------------------------- /doc/img/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadoBuransky/sonar-scoverage-plugin/e424f2cf8cbe5c3fdf8a6bee22eb97ab9de8ea71/doc/img/04.png -------------------------------------------------------------------------------- /parent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.codehaus.sonar-plugins 6 | parent 7 | 19 8 | pom 9 | 10 | Sonar plugins parent 11 | http://sonar-plugins.codehaus.org 12 | 2009 13 | 14 | 15 | 16 | GNU LGPL 3 17 | http://www.gnu.org/licenses/lgpl.txt 18 | repo 19 | 20 | 21 | 22 | 23 | 24 | Sonar dev mailing list 25 | http://xircles.codehaus.org/projects/sonar/lists 26 | http://xircles.codehaus.org/projects/sonar/lists 27 | dev@sonar.codehaus.org 28 | 29 | 30 | 31 | 32 | 33 | ${maven.min.version} 34 | 35 | 36 | 37 | scm:git:https://github.com/SonarSource/parent-oss.git 38 | scm:git:git@github.com:SonarSource/parent-oss.git 39 | https://github.com/SonarSource/parent-oss 40 | 19 41 | 42 | 43 | jira 44 | http://jira.codehaus.org/browse/SONARPLUGINS 45 | 46 | 47 | bamboo 48 | http://bamboo.ci.codehaus.org/browse/SONAR 49 | 50 | 51 | 52 | codehaus.org 53 | Sonar plugins repository 54 | dav:https://dav.codehaus.org/repository/sonar-plugins 55 | 56 | 57 | ${sonar.snapshotRepository.id} 58 | Sonar plugins snapshot repository 59 | false 60 | ${sonar.snapshotRepository.url} 61 | 62 | 63 | 64 | 65 | UTF-8 66 | 3.0.5 67 | 1.7 68 | ${maven.build.timestamp} 69 | yyyy-MM-dd'T'HH:mm:ssZ 70 | codehaus.org 71 | dav:https://dav.codehaus.org/snapshots.repository/sonar-plugins 72 | 73 | 74 | 75 | 76 | 2.5.3 77 | 2.6.1 78 | 3.2 79 | 2.9 80 | 2.8.2 81 | 1.3.1 82 | 2.18 83 | 2.5.2 84 | 2.5 85 | 1.9 86 | 2.10.1 87 | 1.10.b1 88 | 3.3 89 | 2.5.1 90 | 2.7 91 | 2.3 92 | 2.4 93 | 2.18 94 | 3.4 95 | 1.5 96 | 97 | 1.13 98 | 1.3 99 | 1.0-beta-1 100 | 101 | 1.12.1 102 | 1.8 103 | 104 | 105 | GNU LGPL 3 106 | ${project.name} 107 | ${project.inceptionYear} 108 | ${project.organization.name} 109 | dev@sonar.codehaus.org 110 | 111 | 112 | org.codehaus.mojo.signature 113 | java17 114 | 1.0 115 | 116 | 117 | 118 | 119 | 120 | 121 | org.apache.maven.wagon 122 | wagon-webdav 123 | 1.0-beta-2 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | org.codehaus.mojo 132 | animal-sniffer-maven-plugin 133 | ${version.animal-sniffer.plugin} 134 | 135 | 136 | ${animal-sniffer.signature.groupId} 137 | ${animal-sniffer.signature.artifactId} 138 | ${animal-sniffer.signature.version} 139 | 140 | 141 | 142 | 143 | org.apache.maven.plugins 144 | maven-assembly-plugin 145 | ${version.assembly.plugin} 146 | 147 | 148 | 149 | 150 | 420 151 | 152 | 493 153 | 493 154 | 155 | 156 | 157 | 158 | org.codehaus.mojo 159 | buildnumber-maven-plugin 160 | ${version.buildnumber.plugin} 161 | 162 | 163 | org.apache.maven.plugins 164 | maven-clean-plugin 165 | ${version.clean.plugin} 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-compiler-plugin 170 | ${version.compiler.plugin} 171 | 172 | 173 | org.apache.maven.plugins 174 | maven-dependency-plugin 175 | ${version.dependency.plugin} 176 | 177 | 178 | org.apache.maven.plugins 179 | maven-deploy-plugin 180 | ${version.deploy.plugin} 181 | 182 | 183 | org.apache.maven.plugins 184 | maven-enforcer-plugin 185 | ${version.enforcer.plugin} 186 | 187 | 188 | org.apache.maven.plugins 189 | maven-failsafe-plugin 190 | ${version.failsafe.plugin} 191 | 192 | 193 | org.apache.maven.plugins 194 | maven-install-plugin 195 | ${version.install.plugin} 196 | 197 | 198 | org.sonatype.plugins 199 | jarjar-maven-plugin 200 | ${version.jarjar.plugin} 201 | 202 | 203 | com.mycila.maven-license-plugin 204 | maven-license-plugin 205 | ${version.license.plugin} 206 | 207 | 208 | org.apache.maven.plugins 209 | maven-jar-plugin 210 | ${version.jar.plugin} 211 | 212 | 213 | 214 | ${project.version} 215 | 216 | ${buildNumber} 217 | ${timestamp} 218 | 219 | 220 | 221 | 222 | 223 | org.apache.maven.plugins 224 | maven-javadoc-plugin 225 | ${version.javadoc.plugin} 226 | 227 | true 228 | 229 | 230 | 231 | org.apache.maven.plugins 232 | maven-plugin-plugin 233 | ${version.plugin.plugin} 234 | 235 | 236 | org.apache.maven.plugins 237 | maven-release-plugin 238 | ${version.release.plugin} 239 | 240 | https://svn.codehaus.org/sonar-plugins/tags 241 | true 242 | false 243 | 247 | -Prelease 248 | 249 | 250 | 251 | org.apache.maven.plugins 252 | maven-resources-plugin 253 | ${version.resources.plugin} 254 | 255 | 256 | org.apache.maven.plugins 257 | maven-shade-plugin 258 | ${version.shade.plugin} 259 | 260 | 261 | org.apache.maven.plugins 262 | maven-source-plugin 263 | ${version.source.plugin} 264 | 265 | 266 | org.apache.maven.plugins 267 | maven-surefire-plugin 268 | ${version.surefire.plugin} 269 | 270 | 271 | org.apache.maven.plugins 272 | maven-site-plugin 273 | ${version.site.plugin} 274 | 275 | 276 | org.apache.maven.plugins 277 | maven-gpg-plugin 278 | ${version.gpg.plugin} 279 | 280 | 281 | org.codehaus.mojo 282 | native2ascii-maven-plugin 283 | ${version.native2ascii.plugin} 284 | 285 | 286 | org.codehaus.sonar 287 | sonar-packaging-maven-plugin 288 | ${version.sonar-packaging.plugin} 289 | 290 | 291 | org.codehaus.sonar 292 | sonar-dev-maven-plugin 293 | ${version.sonar-dev.plugin} 294 | 295 | 296 | 297 | 298 | 299 | 300 | org.codehaus.mojo 301 | buildnumber-maven-plugin 302 | 303 | 304 | validate 305 | 306 | create 307 | 308 | 309 | 310 | 311 | false 312 | false 313 | true 314 | 0 315 | 316 | 317 | 318 | 319 | org.apache.maven.plugins 320 | maven-compiler-plugin 321 | 322 | ${jdk.min.version} 323 | ${jdk.min.version} 324 | 325 | 326 | 327 | 328 | org.apache.maven.plugins 329 | maven-surefire-plugin 330 | 331 | random 332 | 333 | 334 | 335 | 336 | org.apache.maven.plugins 337 | maven-enforcer-plugin 338 | 339 | 340 | enforce 341 | 342 | enforce 343 | 344 | 345 | 346 | 347 | To build this project Maven ${maven.min.version} (or upper) is required. Please install it. 348 | ${maven.min.version} 349 | 350 | 351 | To build this project JDK ${jdk.min.version} (or upper) is required. Please install it. 352 | ${jdk.min.version} 353 | 354 | 355 | 359 | Build reproducibility : always define plugin versions! 360 | true 361 | true 362 | clean,deploy 363 | 364 | 365 | 366 | Animal-sniffer throws exception when icu4j version 2.6.1 used. 367 | true 368 | 369 | com.ibm.icu:icu4j:[2.6.1] 370 | 371 | 372 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | org.codehaus.mojo 383 | animal-sniffer-maven-plugin 384 | 385 | 386 | enforce-java-api-compatibility 387 | verify 388 | 389 | check 390 | 391 | 392 | 393 | ${animal-sniffer.signature.groupId} 394 | ${animal-sniffer.signature.artifactId} 395 | ${animal-sniffer.signature.version} 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | org.apache.maven.plugins 404 | maven-source-plugin 405 | 406 | 407 | attach-sources 408 | verify 409 | 410 | jar-no-fork 411 | 412 | 413 | 414 | 415 | 416 | 417 | com.mycila.maven-license-plugin 418 | maven-license-plugin 419 | 420 | 421 | org.codehaus.sonar-plugins 422 | license-headers 423 | 1.0 424 | 425 | 426 | 427 |
org/sonar/plugins/licenseheaders/${license.name}.txt
428 | 429 | org/sonar/plugins/licenseheaders/SonarSource.txt 430 | 431 | true 432 | 433 | src/main/java/** 434 | src/test/java/** 435 | 436 | 437 | SLASHSTAR_STYLE 438 | 439 | 440 | ${license.title} 441 | ${license.year} 442 | ${license.owner} 443 | ${license.mailto} 444 | 445 | ${project.build.sourceEncoding} 446 |
447 | 448 | 449 | enforce-license-headers 450 | validate 451 | 452 | check 453 | 454 | 455 | 456 |
457 | 458 | 459 | org.codehaus.sonar 460 | sonar-packaging-maven-plugin 461 | true 462 | 463 | 464 | 465 | 466 | ${buildNumber} 467 | ${timestamp} 468 | 469 | 470 | 471 | 472 |
473 |
474 | 475 | 476 | 477 | skipSanityChecks 478 | 479 | 480 | skipSanityChecks 481 | true 482 | 483 | 484 | 485 | true 486 | true 487 | 488 | 489 | 490 | 491 | release 492 | 493 | 494 | 495 | 496 | org.apache.maven.plugins 497 | maven-javadoc-plugin 498 | 499 | 500 | attach-javadocs 501 | 502 | jar 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | coverage-per-test 512 | 513 | 514 | org.codehaus.sonar-plugins.java 515 | sonar-jacoco-listeners 516 | 1.2 517 | test 518 | 519 | 520 | 521 | 522 | 523 | org.apache.maven.plugins 524 | maven-surefire-plugin 525 | 526 | 527 | 528 | listener 529 | org.sonar.java.jacoco.JUnitListener 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | integration-tests 539 | 540 | 541 | 542 | org.apache.maven.plugins 543 | maven-failsafe-plugin 544 | 545 | 546 | integration-test 547 | integration-test 548 | 549 | integration-test 550 | 551 | 552 | 553 | verify 554 | verify 555 | 556 | verify 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 |
566 | -------------------------------------------------------------------------------- /plugin/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | *.iml 4 | dev -------------------------------------------------------------------------------- /plugin/README.md: -------------------------------------------------------------------------------- 1 | # Sonar Scoverage Plugin source code # 2 | 3 | Useful bash script for plugin development to stop Sonar server, build plugin, copy it to Sonar plugin 4 | directory and start Sonar server again is in the `dev.sh` file. -------------------------------------------------------------------------------- /plugin/dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SONAR_HOME=~/bin/sonarqube-5.4 4 | PLUGIN_VERSION=5.1.3 5 | 6 | mvn install 7 | 8 | PLUGIN_FILE="./target/sonar-scoverage-plugin-$PLUGIN_VERSION.jar" 9 | if [ ! -f $PLUGIN_FILE ]; then 10 | echo "Plugin jar not found! [$PLUGIN_FILE]" 11 | exit 1 12 | fi 13 | 14 | $SONAR_HOME/bin/linux-x86-64/sonar.sh stop 15 | 16 | rm $SONAR_HOME/extensions/plugins/sonar-scoverage-plugin-* 17 | cp $PLUGIN_FILE $SONAR_HOME/extensions/plugins/sonar-scoverage-plugin-$PLUGIN_VERSION.jar 18 | 19 | $SONAR_HOME/bin/linux-x86-64/sonar.sh start 20 | -------------------------------------------------------------------------------- /plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.codehaus.sonar-plugins 8 | parent 9 | 19 10 | ../parent/pom.xml 11 | 12 | 13 | sonar-scoverage-plugin 14 | 5.1.3 15 | sonar-plugin 16 | Sonar Scoverage Plugin 17 | 18 | Sonar plugin for importing statement coverage reports generated by Scoverage. 19 | https://github.com/RadoBuransky/sonar-scoverage-plugin 20 | 2013 21 | 22 | 23 | Rado Buransky 24 | http://www.buransky.com 25 | 26 | 27 | 28 | 29 | radoburansky 30 | Rado Buransky 31 | radoburansky@gmail.com 32 | http://www.buransky.com/ 33 | 34 | 35 | 36 | 48 | 49 | 50 | 51 | GNU LGPL 3 52 | http://www.gnu.org/licenses/lgpl.txt 53 | repo 54 | 55 | 56 | 57 | 58 | 59 | scala-tools 60 | Scala Tools 61 | http://scala-tools.org/repo-releases/ 62 | 63 | true 64 | 65 | 66 | false 67 | 68 | 69 | 70 | 71 | 72 | 5.1 73 | 74 | scoverage 75 | Scoverage 76 | com.buransky.plugins.scoverage.ScoveragePlugin 77 | 78 | 2.11 79 | 2.11.6 80 | 81 | 82 | 83 | 84 | 85 | org.codehaus.sonar 86 | sonar-plugin-api 87 | ${sonar.version} 88 | provided 89 | 90 | 91 | 92 | 93 | org.scala-lang 94 | scala-library 95 | ${scala.full.version} 96 | 97 | 98 | org.scala-lang.modules 99 | scala-xml_${scala.version} 100 | 1.0.3 101 | 102 | 103 | 104 | 105 | org.scalatest 106 | scalatest_${scala.version} 107 | 2.2.4 108 | test 109 | 110 | 111 | junit 112 | junit 113 | 4.11 114 | test 115 | 116 | 117 | org.mockito 118 | mockito-all 119 | 1.9.5 120 | test 121 | 122 | 123 | 124 | 125 | net.sourceforge.findbugs 126 | jsr305 127 | 1.3.7 128 | provided 129 | 130 | 131 | org.apache.maven 132 | maven-project 133 | 2.2.1 134 | provided 135 | 136 | 137 | org.eclipse.persistence 138 | javax.persistence 139 | 2.1.0 140 | compile 141 | 142 | 143 | 144 | 145 | 146 | 147 | org.scala-tools 148 | maven-scala-plugin 149 | 2.15.2 150 | 151 | 152 | scala-compile 153 | process-resources 154 | 155 | compile 156 | 157 | 158 | 159 | scala-test-compile 160 | process-test-resources 161 | 162 | testCompile 163 | 164 | 165 | 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-surefire-plugin 170 | 171 | 172 | **/*Spec.class 173 | 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /plugin/src/main/resources/com/buransky/plugins/scoverage/widget.html.erb: -------------------------------------------------------------------------------- 1 | <% measure=measure('scoverage') 2 | if measure 3 | %> 4 |
5 |

6 | Statement coverage : <%= format_measure(measure, :suffix => ' %') %> 7 | <%= dashboard_configuration.selected_period? ? format_variation(measure) : trend_icon(measure) -%> 8 |

9 |
10 | <% end %> -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/ScoverageExtensionProvider.scala: -------------------------------------------------------------------------------- 1 | package com.buransky.plugins.scoverage 2 | 3 | import com.buransky.plugins.scoverage.language.Scala 4 | import org.sonar.api.resources.Languages 5 | import org.sonar.api.{Extension, ExtensionProvider} 6 | 7 | import scala.collection.JavaConversions._ 8 | import scala.collection.mutable.ListBuffer 9 | 10 | class ScoverageExtensionProvider(languages: Languages) extends ExtensionProvider { 11 | override def provide(): java.util.List[Class[_ <: Extension]] = { 12 | val result = ListBuffer[Class[_ <: Extension]]() 13 | 14 | if (languages.get(Scala.key) == null) { 15 | // Fix issue with multiple Scala plugins: 16 | // https://github.com/RadoBuransky/sonar-scoverage-plugin/issues/31 17 | result += classOf[Scala] 18 | } 19 | 20 | result 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/ScoveragePlugin.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage 21 | 22 | import com.buransky.plugins.scoverage.measure.ScalaMetrics 23 | import com.buransky.plugins.scoverage.sensor.ScoverageSensor 24 | import com.buransky.plugins.scoverage.widget.ScoverageWidget 25 | import org.sonar.api.{Extension, SonarPlugin} 26 | 27 | import scala.collection.JavaConversions._ 28 | import scala.collection.mutable.ListBuffer 29 | 30 | /** 31 | * Plugin entry point. 32 | * 33 | * @author Rado Buransky 34 | */ 35 | class ScoveragePlugin extends SonarPlugin { 36 | override def getExtensions: java.util.List[Class[_ <: Extension]] = 37 | ListBuffer( 38 | classOf[ScoverageExtensionProvider], 39 | classOf[ScalaMetrics], 40 | classOf[ScoverageSensor], 41 | classOf[ScoverageWidget] 42 | ) 43 | 44 | override val toString = getClass.getSimpleName 45 | } 46 | -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/ScoverageReportParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage 21 | 22 | import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer 23 | 24 | /** 25 | * Interface for Scoverage report parser. 26 | * 27 | * @author Rado Buransky 28 | */ 29 | trait ScoverageReportParser { 30 | def parse(reportFilePath: String, pathSanitizer: PathSanitizer): ProjectStatementCoverage 31 | } 32 | 33 | /** 34 | * Common Scoverage exception. 35 | * 36 | * @author Rado Buransky 37 | */ 38 | case class ScoverageException(message: String, source: Throwable = null) 39 | extends Exception(message, source) 40 | -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/StatementCoverage.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage 21 | 22 | /** 23 | * Statement coverage represents rate at which are statements of a certain source code unit 24 | * being covered by tests. 25 | * 26 | * @author Rado Buransky 27 | */ 28 | sealed trait StatementCoverage { 29 | /** 30 | * Percentage rate ranging from 0 up to 100%. 31 | */ 32 | lazy val rate: Double = 33 | if (statementCount == 0) 34 | 0.0 35 | else 36 | (coveredStatementsCount.toDouble / statementCount.toDouble) * 100.0 37 | 38 | lazy val branchRate: Double = 39 | if (branchCount == 0) 40 | 0.0 41 | else 42 | (coveredBranchesCount.toDouble / branchCount.toDouble) * 100.0 43 | 44 | /** 45 | * Total number of all statements within the source code unit, 46 | */ 47 | def statementCount: Int 48 | 49 | /** 50 | * Total number of all branches within the source code unit, 51 | */ 52 | def branchCount: Int 53 | 54 | /** 55 | * Number of statements covered by unit tests. 56 | */ 57 | def coveredStatementsCount: Int 58 | 59 | /** 60 | * Number of branches covered by unit tests. 61 | */ 62 | def coveredBranchesCount: Int 63 | 64 | require(statementCount >= 0, "Statements count cannot be negative! [" + statementCount + "]") 65 | require(coveredStatementsCount >= 0, "Statements count cannot be negative! [" + 66 | coveredStatementsCount + "]") 67 | require(coveredStatementsCount <= statementCount, 68 | "Number of covered statements cannot be more than total number of statements! [" + 69 | statementCount + ", " + coveredStatementsCount + "]") 70 | } 71 | 72 | /** 73 | * Allows to build tree structure from state coverage values. 74 | */ 75 | trait NodeStatementCoverage extends StatementCoverage { 76 | def name: String 77 | def children: Iterable[NodeStatementCoverage] 78 | def statementSum: Int = children.map(_.statementSum).sum 79 | def branchesSum: Int = children.map(_.branchesSum).sum 80 | def coveredStatementsSum: Int = children.map(_.coveredStatementsSum).sum 81 | def coveredBranchesSum: Int = children.map(_.coveredBranchesSum).sum 82 | } 83 | 84 | /** 85 | * Root node. In multi-module projects it can contain other ProjectStatementCoverage 86 | * elements as children. 87 | */ 88 | case class ProjectStatementCoverage(name: String, children: Iterable[NodeStatementCoverage]) 89 | extends NodeStatementCoverage { 90 | // projects' coverage values are defined as sums of their child values 91 | val statementCount = statementSum 92 | val coveredStatementsCount = coveredStatementsSum 93 | val branchCount = branchesSum 94 | val coveredBranchesCount = coveredBranchesSum 95 | } 96 | 97 | /** 98 | * Physical directory in file system. 99 | */ 100 | case class DirectoryStatementCoverage(name: String, children: Iterable[NodeStatementCoverage]) 101 | extends NodeStatementCoverage { 102 | // directories' coverage values are defined as sums of their DIRECT child values 103 | val statementCount = children.filter(_.isInstanceOf[FileStatementCoverage]).map(_.statementCount).sum 104 | val coveredStatementsCount = children.filter(_.isInstanceOf[FileStatementCoverage]).map(_.coveredStatementsCount).sum 105 | val branchCount = children.filter(_.isInstanceOf[FileStatementCoverage]).map(_.branchCount).sum 106 | val coveredBranchesCount = children.filter(_.isInstanceOf[FileStatementCoverage]).map(_.coveredBranchesCount).sum 107 | } 108 | 109 | /** 110 | * Scala source code file. 111 | */ 112 | case class FileStatementCoverage(name: String, statementCount: Int, coveredStatementsCount: Int, 113 | statements: Iterable[CoveredStatement]) extends NodeStatementCoverage { 114 | // leaf implementation sums==values 115 | val children = List.empty[NodeStatementCoverage] 116 | override val statementSum = statementCount 117 | override val coveredStatementsSum = coveredStatementsCount 118 | override val branchCount = statements.count(_.branch) 119 | override val coveredBranchesCount = statements.count(s => s.branch && s.hitCount > 0) 120 | } 121 | 122 | /** 123 | * Position a Scala source code file. 124 | */ 125 | case class StatementPosition(line: Int, pos: Int) 126 | 127 | /** 128 | * Coverage information about the Scala statement. 129 | * 130 | * @param start Starting position of the statement. 131 | * @param end Ending position of the statement. 132 | * @param hitCount How many times has the statement been hit by unit tests. Zero means 133 | * that the statement is not covered. 134 | */ 135 | case class CoveredStatement(start: StatementPosition, end: StatementPosition, hitCount: Int, branch: Boolean) 136 | 137 | /** 138 | * Aggregated statement coverage for a given source code line. 139 | */ 140 | case class CoveredLine(line: Int, hitCount: Int, conditions: Int, coveredConditions: Int) 141 | 142 | object StatementCoverage { 143 | /** 144 | * Utility method to transform statement coverage to line coverage. Pessimistic logic is used 145 | * meaning that line hit count is minimum of hit counts of all statements on the given line. 146 | * 147 | * Example: If a line contains two statements, one is covered by 3 hits, the other one is 148 | * without any hits, then the whole line is treated as uncovered. 149 | * 150 | * @param statements Statement coverage. 151 | * @return Line coverage. 152 | */ 153 | def statementCoverageToLineCoverage(statements: Iterable[CoveredStatement]): Iterable[CoveredLine] = { 154 | // Handle statements that end on a different line than start 155 | val multilineStatements = statements.filter { s => s.start.line != s.end.line } 156 | val extraStatements = multilineStatements.flatMap { s => 157 | for (i <- (s.start.line + 1) to s.end.line) 158 | yield CoveredStatement(StatementPosition(i, 0), StatementPosition(i, 0), s.hitCount, s.branch) 159 | } 160 | 161 | // Group statements by starting line 162 | val lineStatements = (statements ++ extraStatements).groupBy(_.start.line) 163 | 164 | // Pessimistic approach: line hit count is a minimum of hit counts of all statements on the line 165 | lineStatements.map { case (line, lineStatement) => 166 | CoveredLine(line, lineStatement.map(_.hitCount).min, lineStatement.count(_.branch), lineStatement.count(s => s.branch && s.hitCount > 0)) 167 | } 168 | } 169 | } -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/language/Scala.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.language 21 | 22 | import org.sonar.api.resources.AbstractLanguage 23 | 24 | /** 25 | * Scala language. 26 | * 27 | * @author Rado Buransky 28 | */ 29 | class Scala extends AbstractLanguage(Scala.key, Scala.name) { 30 | val getFileSuffixes = Array(Scala.fileExtension) 31 | } 32 | 33 | object Scala { 34 | val key = "scala" 35 | val name = "Scala" 36 | val fileExtension = "scala" 37 | } -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/measure/ScalaMetrics.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.measure 21 | 22 | import org.sonar.api.measures.{CoreMetrics, Metric, Metrics} 23 | import org.sonar.api.measures.Metric.ValueType 24 | import scala.collection.JavaConversions._ 25 | import scala.collection.mutable.ListBuffer 26 | 27 | /** 28 | * Statement coverage metric definition. 29 | * 30 | * @author Rado Buransky 31 | */ 32 | class ScalaMetrics extends Metrics { 33 | override def getMetrics = ListBuffer(ScalaMetrics.statementCoverage, ScalaMetrics.coveredStatements, ScalaMetrics.totalStatements).toList 34 | } 35 | 36 | object ScalaMetrics { 37 | private val STATEMENT_COVERAGE_KEY = "scoverage" 38 | private val COVERED_STATEMENTS_KEY = "covered_statements" 39 | private val TOTAL_STATEMENTS_KEY = "total_statements" 40 | 41 | lazy val statementCoverage = new Metric.Builder(STATEMENT_COVERAGE_KEY, 42 | "Statement coverage", ValueType.PERCENT) 43 | .setDescription("Statement coverage by tests") 44 | .setDirection(Metric.DIRECTION_BETTER) 45 | .setQualitative(true) 46 | .setDomain(CoreMetrics.DOMAIN_TESTS) 47 | .setWorstValue(0.0) 48 | .setBestValue(100.0) 49 | .create[java.lang.Double]() 50 | 51 | lazy val coveredStatements = new Metric.Builder(COVERED_STATEMENTS_KEY, 52 | "Covered statements", Metric.ValueType.INT) 53 | .setDescription("Number of statements covered by tests") 54 | .setDirection(Metric.DIRECTION_BETTER) 55 | .setQualitative(false) 56 | .setDomain(CoreMetrics.DOMAIN_SIZE) 57 | .create[java.lang.Integer]() 58 | 59 | lazy val totalStatements = new Metric.Builder(TOTAL_STATEMENTS_KEY, 60 | "Total statements", Metric.ValueType.INT) 61 | .setDescription("Number of all statements covered by tests and uncovered") 62 | .setDirection(Metric.DIRECTION_BETTER) 63 | .setQualitative(false) 64 | .setDomain(CoreMetrics.DOMAIN_SIZE) 65 | .create[java.lang.Integer]() 66 | } -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/pathcleaner/BruteForceSequenceMatcher.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.pathcleaner 21 | 22 | import java.io.File 23 | import org.apache.commons.io.FileUtils 24 | import org.apache.commons.io.FileUtils 25 | import BruteForceSequenceMatcher._ 26 | import com.buransky.plugins.scoverage.util.PathUtil 27 | import scala.collection.JavaConversions._ 28 | import org.sonar.api.utils.log.Loggers 29 | 30 | object BruteForceSequenceMatcher { 31 | 32 | val extensions = Array[String]("java", "scala") 33 | 34 | type PathSeq = Seq[String] 35 | } 36 | 37 | /** 38 | * Helper that allows to convert a report path into a source folder relative path by testing it against 39 | * the tree of source files. 40 | * 41 | * Assumes that all report paths of a given report have a common root. Dependent of the scoverage 42 | * report this root is either something outside the actual project (absolute path), the base dir of the project 43 | * (report path relative to base dir) or some sub folder of the project. 44 | * 45 | * By reverse mapping a report path against the tree of all file children of the source folder the correct filesystem file 46 | * can be found and the report path can be converted into a source dir relative path. * 47 | * 48 | * @author Michael Zinsmaier 49 | */ 50 | class BruteForceSequenceMatcher(baseDir: File, sourcePath: String) extends PathSanitizer { 51 | 52 | private val sourceDir = initSourceDir() 53 | require(sourceDir.isAbsolute) 54 | require(sourceDir.isDirectory) 55 | 56 | private val log = Loggers.get(classOf[BruteForceSequenceMatcher]) 57 | private val sourcePathLength = PathUtil.splitPath(sourceDir.getAbsolutePath).size 58 | private val filesMap = initFilesMap() 59 | 60 | 61 | def getSourceRelativePath(reportPath: PathSeq): Option[PathSeq] = { 62 | // match with file system map of files 63 | val relPathOption = for { 64 | absPathCandidates <- filesMap.get(reportPath.last) 65 | path <- absPathCandidates.find(absPath => absPath.endsWith(reportPath)) 66 | } yield path.drop(sourcePathLength) 67 | 68 | relPathOption 69 | } 70 | 71 | // mock able helpers that allow us to remove the dependency to the real file system during tests 72 | 73 | private[pathcleaner] def initSourceDir(): File = { 74 | val sourceDir = new File(baseDir, sourcePath) 75 | sourceDir 76 | } 77 | 78 | private[pathcleaner] def initFilesMap(): Map[String, Seq[PathSeq]] = { 79 | val srcFiles = FileUtils.iterateFiles(sourceDir, extensions, true) 80 | val paths = srcFiles.map(file => PathUtil.splitPath(file.getAbsolutePath)).toSeq 81 | 82 | // group them by filename, in case multiple files have the same name 83 | paths.groupBy(path => path.last) 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/pathcleaner/PathSanitizer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.pathcleaner 21 | 22 | /** 23 | * @author Michael Zinsmaier 24 | */ 25 | trait PathSanitizer { 26 | 27 | /** tries to convert the given path such that it is relative to the 28 | * projects/modules source directory. 29 | * 30 | * @return Some(source folder relative path) or None if the path cannot be converted 31 | */ 32 | def getSourceRelativePath(path: Seq[String]): Option[Seq[String]] 33 | 34 | } -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.sensor 21 | 22 | import com.buransky.plugins.scoverage.language.Scala 23 | import com.buransky.plugins.scoverage.measure.ScalaMetrics 24 | import com.buransky.plugins.scoverage.pathcleaner.{BruteForceSequenceMatcher, PathSanitizer} 25 | import com.buransky.plugins.scoverage.util.LogUtil 26 | import com.buransky.plugins.scoverage.xml.XmlScoverageReportParser 27 | import com.buransky.plugins.scoverage.{CoveredStatement, DirectoryStatementCoverage, FileStatementCoverage, _} 28 | import org.sonar.api.batch.fs.{FileSystem, InputFile, InputPath} 29 | import org.sonar.api.batch.{CoverageExtension, Sensor, SensorContext} 30 | import org.sonar.api.config.Settings 31 | import org.sonar.api.measures.{CoverageMeasuresBuilder, Measure} 32 | import org.sonar.api.resources.{Project, Resource} 33 | import org.sonar.api.scan.filesystem.PathResolver 34 | import org.sonar.api.utils.log.Loggers 35 | 36 | import scala.collection.JavaConversions._ 37 | 38 | /** 39 | * Main sensor for importing Scoverage report to Sonar. 40 | * 41 | * @author Rado Buransky 42 | */ 43 | class ScoverageSensor(settings: Settings, pathResolver: PathResolver, fileSystem: FileSystem) 44 | extends Sensor with CoverageExtension { 45 | private val log = Loggers.get(classOf[ScoverageSensor]) 46 | protected val SCOVERAGE_REPORT_PATH_PROPERTY = "sonar.scoverage.reportPath" 47 | protected lazy val scoverageReportParser: ScoverageReportParser = XmlScoverageReportParser() 48 | 49 | override def shouldExecuteOnProject(project: Project): Boolean = fileSystem.languages().contains(Scala.key) 50 | 51 | override def analyse(project: Project, context: SensorContext) { 52 | scoverageReportPath match { 53 | case Some(reportPath) => 54 | // Single-module project 55 | val srcOption = Option(settings.getString("sonar.sources")) 56 | val sonarSources = srcOption match { 57 | case Some(src) => src 58 | case None => { 59 | log.warn(s"could not find settings key sonar.sources assuming src/main/scala.") 60 | "src/main/scala" 61 | } 62 | } 63 | val pathSanitizer = createPathSanitizer(sonarSources) 64 | processProject(scoverageReportParser.parse(reportPath, pathSanitizer), project, context, sonarSources) 65 | 66 | case None => 67 | // Multi-module project has report path set for each module individually 68 | analyseMultiModuleProject(project, context) 69 | } 70 | } 71 | 72 | override val toString = getClass.getSimpleName 73 | 74 | protected def createPathSanitizer(sonarSources: String): PathSanitizer 75 | = new BruteForceSequenceMatcher(fileSystem.baseDir(), sonarSources) 76 | 77 | private lazy val scoverageReportPath: Option[String] = { 78 | settings.getString(SCOVERAGE_REPORT_PATH_PROPERTY) match { 79 | case null => None 80 | case path: String => 81 | pathResolver.relativeFile(fileSystem.baseDir, path) match { 82 | case report: java.io.File if !report.exists || !report.isFile => 83 | log.error(LogUtil.f("Report not found at {}"), report) 84 | None 85 | 86 | case report: java.io.File => Some(report.getAbsolutePath) 87 | } 88 | } 89 | } 90 | 91 | private def analyseMultiModuleProject(project: Project, context: SensorContext) { 92 | project.isModule match { 93 | case true => log.warn(LogUtil.f("Report path not set for " + project.name + " module! [" + 94 | project.name + "." + SCOVERAGE_REPORT_PATH_PROPERTY + "]")) 95 | case _ => 96 | // Compute overall statement coverage from submodules 97 | val totalStatementCount = project.getModules.map(analyseStatementCountForModule(_, context)).sum 98 | val coveredStatementCount = project.getModules.map(analyseCoveredStatementCountForModule(_, context)).sum 99 | 100 | if (totalStatementCount > 0) { 101 | // Convert to percentage 102 | val overall = (coveredStatementCount.toDouble / totalStatementCount.toDouble) * 100.0 103 | 104 | // Set overall statement coverage 105 | context.saveMeasure(project, createStatementCoverage(overall)) 106 | 107 | log.info(LogUtil.f("Overall statement coverage is " + ("%1.2f" format overall))) 108 | } 109 | } 110 | } 111 | 112 | private def analyseCoveredStatementCountForModule(module: Project, context: SensorContext): Long = { 113 | // Aggregate modules 114 | context.getMeasure(module, ScalaMetrics.coveredStatements) match { 115 | case null => 116 | log.debug(LogUtil.f("Module has no statement coverage. [" + module.name + "]")) 117 | 0 118 | case moduleCoveredStatementCount: Measure[_] => 119 | log.debug(LogUtil.f("Covered statement count for " + module.name + " module. [" + 120 | moduleCoveredStatementCount.getValue + "]")) 121 | 122 | moduleCoveredStatementCount.getValue.toLong 123 | } 124 | } 125 | 126 | private def analyseStatementCountForModule(module: Project, context: SensorContext): Long = { 127 | // Aggregate modules 128 | context.getMeasure(module, ScalaMetrics.totalStatements) match { 129 | case null => 130 | log.debug(LogUtil.f("Module has no number of statements. [" + module.name + "]")) 131 | 0 132 | 133 | case moduleStatementCount: Measure[_] => 134 | log.debug(LogUtil.f("Statement count for " + module.name + " module. [" + 135 | moduleStatementCount.getValue + "]")) 136 | 137 | moduleStatementCount.getValue.toLong 138 | } 139 | } 140 | 141 | private def processProject(projectCoverage: ProjectStatementCoverage, project: Project, context: SensorContext, sonarSources: String) { 142 | // Save measures 143 | saveMeasures(context, project, projectCoverage) 144 | 145 | log.info(LogUtil.f("Statement coverage for " + project.getKey + " is " + ("%1.2f" format projectCoverage.rate))) 146 | 147 | // Process children 148 | processChildren(projectCoverage.children, context, sonarSources) 149 | } 150 | 151 | private def processDirectory(directoryCoverage: DirectoryStatementCoverage, context: SensorContext, parentDirectory: String) { 152 | // save measures if any 153 | if (directoryCoverage.statementCount > 0) { 154 | val path = appendFilePath(parentDirectory, directoryCoverage.name) 155 | 156 | getResource(path, context, false) match { 157 | case Some(srcDir) => { 158 | // Save directory measures 159 | saveMeasures(context, srcDir, directoryCoverage) 160 | } 161 | case None => 162 | } 163 | } 164 | // Process children 165 | processChildren(directoryCoverage.children, context, appendFilePath(parentDirectory, directoryCoverage.name)) 166 | } 167 | 168 | private def processFile(fileCoverage: FileStatementCoverage, context: SensorContext, directory: String) { 169 | val path = appendFilePath(directory, fileCoverage.name) 170 | 171 | getResource(path, context, true) match { 172 | case Some(scalaSourceFile) => { 173 | // Save measures 174 | saveMeasures(context, scalaSourceFile, fileCoverage) 175 | // Save line coverage. This is needed just for source code highlighting. 176 | saveLineCoverage(fileCoverage.statements, scalaSourceFile, context) 177 | } 178 | case None => 179 | } 180 | } 181 | 182 | private def getResource(path: String, context: SensorContext, isFile: Boolean): Option[Resource] = { 183 | 184 | val inputOption: Option[InputPath] = if (isFile) { 185 | val p = fileSystem.predicates() 186 | Option(fileSystem.inputFile(p.and( 187 | p.hasRelativePath(path), 188 | p.hasLanguage(Scala.key), 189 | p.hasType(InputFile.Type.MAIN)))) 190 | } else { 191 | Option(fileSystem.inputDir(pathResolver.relativeFile(fileSystem.baseDir(), path))) 192 | } 193 | 194 | inputOption match { 195 | case Some(path: InputPath) => 196 | Some(context.getResource(path)) 197 | case None => { 198 | log.warn(s"File or directory not found in file system! ${path}") 199 | None 200 | } 201 | } 202 | } 203 | 204 | private def saveMeasures(context: SensorContext, resource: Resource, statementCoverage: StatementCoverage) { 205 | context.saveMeasure(resource, createStatementCoverage(statementCoverage.rate)) 206 | context.saveMeasure(resource, createStatementCount(statementCoverage.statementCount)) 207 | context.saveMeasure(resource, createCoveredStatementCount(statementCoverage.coveredStatementsCount)) 208 | 209 | log.debug(LogUtil.f("Save measures [" + statementCoverage.rate + ", " + statementCoverage.statementCount + 210 | ", " + statementCoverage.coveredStatementsCount + ", " + statementCoverage.branchRate + ", " + resource.getKey + "]")) 211 | } 212 | 213 | private def saveLineCoverage(coveredStatements: Iterable[CoveredStatement], resource: Resource, 214 | context: SensorContext) { 215 | // Convert statements to lines 216 | val coveredLines = StatementCoverage.statementCoverageToLineCoverage(coveredStatements) 217 | 218 | // Set line hits 219 | val coverage = CoverageMeasuresBuilder.create() 220 | coveredLines.foreach { coveredLine => 221 | coverage.setHits(coveredLine.line, coveredLine.hitCount) 222 | coverage.setConditions(coveredLine.line, coveredLine.conditions, coveredLine.coveredConditions) 223 | } 224 | 225 | // Save measures 226 | coverage.createMeasures().toList.foreach(context.saveMeasure(resource, _)) 227 | } 228 | 229 | private def processChildren(children: Iterable[StatementCoverage], context: SensorContext, directory: String) { 230 | children.foreach(processChild(_, context, directory)) 231 | } 232 | 233 | private def processChild(dirOrFile: StatementCoverage, context: SensorContext, directory: String) { 234 | dirOrFile match { 235 | case dir: DirectoryStatementCoverage => processDirectory(dir, context, directory) 236 | case file: FileStatementCoverage => processFile(file, context, directory) 237 | case _ => throw new IllegalStateException("Not a file or directory coverage! [" + 238 | dirOrFile.getClass.getName + "]") 239 | } 240 | } 241 | 242 | private def createStatementCoverage[T <: Serializable](rate: Double): Measure[T] = 243 | new Measure[T](ScalaMetrics.statementCoverage, rate) 244 | 245 | private def createStatementCount[T <: Serializable](statements: Int): Measure[T] = 246 | new Measure(ScalaMetrics.totalStatements, statements.toDouble, 0) 247 | 248 | private def createCoveredStatementCount[T <: Serializable](coveredStatements: Int): Measure[T] = 249 | new Measure(ScalaMetrics.coveredStatements, coveredStatements.toDouble, 0) 250 | 251 | private def appendFilePath(src: String, name: String) = { 252 | val result = src match { 253 | case java.io.File.separator => java.io.File.separator 254 | case empty if empty.isEmpty => "" 255 | case other => other + java.io.File.separator 256 | } 257 | 258 | result + name 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/util/LogUtil.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.util 21 | 22 | /** 23 | * Logging helper. 24 | * 25 | * @author Rado Buransky 26 | */ 27 | object LogUtil { 28 | def f(msg: String) = "[scoverage] " + msg 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/util/PathUtil.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.util 21 | 22 | import java.io.File 23 | import scala.Iterator 24 | /** 25 | * File path helper. 26 | * 27 | * @author Rado Buransky 28 | */ 29 | object PathUtil { 30 | 31 | def splitPath(filePath: String): List[String] = { 32 | new FileParentIterator(new File(filePath)).toList.reverse 33 | } 34 | 35 | class FileParentIterator(private var f: File) extends Iterator[String] { 36 | def hasNext: Boolean = f != null && !f.getName().isEmpty() 37 | def next(): String = { 38 | val name = f.getName() 39 | f = f.getParentFile 40 | name 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/widget/ScoverageWidget.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.widget 21 | 22 | import org.sonar.api.web.{RubyRailsWidget, AbstractRubyTemplate} 23 | 24 | /** 25 | * UI widget that can be added to the main dashboard to display overall statement coverage for the project. 26 | * 27 | * @author Rado Buransky 28 | */ 29 | class ScoverageWidget extends AbstractRubyTemplate with RubyRailsWidget { 30 | val getId = "scoverage" 31 | val getTitle = "Statement coverage" 32 | override val getTemplatePath = "/com/buransky/plugins/scoverage/widget.html.erb" 33 | } 34 | -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.xml 21 | 22 | import java.io.File 23 | 24 | import com.buransky.plugins.scoverage._ 25 | import com.buransky.plugins.scoverage.util.PathUtil 26 | import org.sonar.api.utils.log.Loggers 27 | 28 | import scala.annotation.tailrec 29 | import scala.collection.mutable 30 | import scala.io.Source 31 | import scala.xml.parsing.ConstructingParser 32 | import scala.xml.{MetaData, NamespaceBinding, Text} 33 | import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer 34 | 35 | /** 36 | * Scoverage XML parser based on ConstructingParser provided by standard Scala library. 37 | * 38 | * @author Rado Buransky 39 | */ 40 | class XmlScoverageReportConstructingParser(source: Source, pathSanitizer: PathSanitizer) extends ConstructingParser(source, false) { 41 | private val log = Loggers.get(classOf[XmlScoverageReportConstructingParser]) 42 | 43 | private val CLASS_ELEMENT = "class" 44 | private val FILENAME_ATTRIBUTE = "filename" 45 | private val STATEMENT_ELEMENT = "statement" 46 | private val START_ATTRIBUTE = "start" 47 | private val LINE_ATTRIBUTE = "line" 48 | private val BRANCH_ATTRIBUTE = "branch" 49 | private val INVOCATION_COUNT_ATTRIBUTE = "invocation-count" 50 | 51 | val statementsInFile: mutable.HashMap[String, List[CoveredStatement]] = mutable.HashMap.empty 52 | var currentFilePath: Option[String] = None 53 | 54 | def parse(): ProjectStatementCoverage = { 55 | // Initialize 56 | nextch() 57 | 58 | // Parse 59 | document() 60 | 61 | // Transform map to project 62 | projectFromMap(statementsInFile.toMap) 63 | } 64 | 65 | override def elemStart(pos: Int, pre: String, label: String, attrs: MetaData, scope: NamespaceBinding) { 66 | label match { 67 | case CLASS_ELEMENT => 68 | currentFilePath = Some(fixLeadingSlash(getText(attrs, FILENAME_ATTRIBUTE))) 69 | log.debug("Current file path: " + currentFilePath.get) 70 | 71 | case STATEMENT_ELEMENT => 72 | currentFilePath match { 73 | case Some(cfp) => 74 | val start = getInt(attrs, START_ATTRIBUTE) 75 | val line = getInt(attrs, LINE_ATTRIBUTE) 76 | val hits = getInt(attrs, INVOCATION_COUNT_ATTRIBUTE) 77 | val branch = getBoolean(attrs, BRANCH_ATTRIBUTE) 78 | 79 | // Add covered statement to the mutable map 80 | val pos = StatementPosition(line, start) 81 | addCoveredStatement(cfp, CoveredStatement(pos, pos, hits, branch)) 82 | 83 | log.debug("Statement added: " + line + ", " + hits + ", " + start) 84 | 85 | case None => throw new ScoverageException("Current file path not set!") 86 | } 87 | case _ => // Nothing to do 88 | } 89 | 90 | super.elemStart(pos, pre, label, attrs, scope) 91 | } 92 | 93 | private def addCoveredStatement(filePath: String, coveredStatement: CoveredStatement) { 94 | statementsInFile.get(filePath) match { 95 | case None => statementsInFile.put(filePath, List(coveredStatement)) 96 | case Some(s) => statementsInFile.update(filePath, coveredStatement :: s) 97 | } 98 | } 99 | 100 | /** 101 | * Remove this when scoverage is fixed! It's just a hack. 102 | * Old Scoverage has incorrectly added leading '/' to relative file paths. 103 | */ 104 | private def fixLeadingSlash(filePath: String) = { 105 | if (filePath.startsWith(File.separator) && !new File(filePath).exists()) { 106 | filePath.drop(File.separator.length) 107 | } 108 | else 109 | filePath 110 | } 111 | 112 | private def getBoolean(attrs: MetaData, name: String) = getText(attrs, name).toBoolean 113 | 114 | private def getInt(attrs: MetaData, name: String) = getText(attrs, name).toInt 115 | 116 | private def getText(attrs: MetaData, name: String): String = { 117 | attrs.get(name) match { 118 | case Some(attr) => 119 | attr match { 120 | case text: Text => text.toString() 121 | case _ => throw new ScoverageException("Not a text attribute!") 122 | } 123 | case None => throw new ScoverageException("Attribute doesn't exit! [" + name + "]") 124 | } 125 | } 126 | 127 | private case class DirOrFile(name: String, var children: List[DirOrFile], 128 | coverage: Option[FileStatementCoverage]) { 129 | def get(name: String) = children.find(_.name == name) 130 | 131 | @tailrec 132 | final def add(chain: DirOrFile) { 133 | get(chain.name) match { 134 | case None => children = chain :: children 135 | case Some(child) => 136 | chain.children match { 137 | case h :: t => 138 | if (t != Nil) 139 | throw new IllegalStateException("This is not a linear chain!") 140 | child.add(h) 141 | case _ => // Duplicate file? Should not happen. 142 | } 143 | } 144 | } 145 | 146 | def toStatementCoverage: NodeStatementCoverage = { 147 | val childNodes = children.map(_.toStatementCoverage) 148 | 149 | childNodes match { 150 | case Nil => coverage.get 151 | case _ => DirectoryStatementCoverage(name, childNodes) 152 | } 153 | } 154 | 155 | def toProjectStatementCoverage: ProjectStatementCoverage = { 156 | toStatementCoverage match { 157 | case node: NodeStatementCoverage => ProjectStatementCoverage("", node.children) 158 | case _ => throw new ScoverageException("Illegal statement coverage!") 159 | } 160 | } 161 | } 162 | 163 | private def projectFromMap(statementsInFile: Map[String, List[CoveredStatement]]): 164 | ProjectStatementCoverage = { 165 | 166 | // Transform to file statement coverage 167 | val files = fileStatementCoverage(statementsInFile) 168 | 169 | // Transform file paths to chain of case classes 170 | val chained = files.map(fsc => pathToChain(fsc._1, fsc._2)).flatten 171 | 172 | // Merge chains into one tree 173 | val root = DirOrFile("", Nil, None) 174 | chained.foreach(root.add) 175 | 176 | // Transform file system tree into coverage structure tree 177 | root.toProjectStatementCoverage 178 | } 179 | 180 | private def pathToChain(filePath: String, coverage: FileStatementCoverage): Option[DirOrFile] = { 181 | // helper 182 | def convertToDirOrFile(relPath: Seq[String]) = { 183 | // Get directories 184 | val dirs = for (i <- 0 to relPath.length - 2) 185 | yield DirOrFile(relPath(i), Nil, None) 186 | 187 | // Chain directories 188 | for (i <- 0 to dirs.length - 2) 189 | dirs(i).children = List(dirs(i + 1)) 190 | 191 | // Get file 192 | val file = DirOrFile(relPath(relPath.length - 1).toString, Nil, Some(coverage)) 193 | 194 | if (dirs.isEmpty) { 195 | // File in root dir 196 | file 197 | } else { 198 | // Append file 199 | dirs.last.children = List(file) 200 | dirs.head 201 | } 202 | } 203 | 204 | // processing 205 | val path = PathUtil.splitPath(filePath) 206 | 207 | if (path.length < 1) 208 | throw new ScoverageException("Path cannot be empty!") 209 | 210 | pathSanitizer.getSourceRelativePath(path) match { 211 | case Some(relPath) => Some(convertToDirOrFile(relPath)) 212 | case None => { 213 | log.warn(s"skipping file coverage results for $path, was not able to retrieve the file in the configured source dir") 214 | None 215 | } 216 | } 217 | } 218 | 219 | private def fileStatementCoverage(statementsInFile: Map[String, List[CoveredStatement]]): 220 | Map[String, FileStatementCoverage] = { 221 | statementsInFile.map { sif => 222 | val fileStatementCoverage = FileStatementCoverage(PathUtil.splitPath(sif._1).last, 223 | sif._2.length, coveredStatements(sif._2), sif._2) 224 | 225 | (sif._1, fileStatementCoverage) 226 | } 227 | } 228 | 229 | private def coveredStatements(statements: Iterable[CoveredStatement]) = 230 | statements.count(_.hitCount > 0) 231 | } -------------------------------------------------------------------------------- /plugin/src/main/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.xml 21 | 22 | import com.buransky.plugins.scoverage.util.LogUtil 23 | import com.buransky.plugins.scoverage.{ProjectStatementCoverage, ScoverageException, ScoverageReportParser} 24 | import org.sonar.api.utils.log.Loggers 25 | 26 | import scala.io.Source 27 | import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer 28 | 29 | /** 30 | * Bridge between parser implementation and coverage provider. 31 | * 32 | * @author Rado Buransky 33 | */ 34 | class XmlScoverageReportParser extends ScoverageReportParser { 35 | private val log = Loggers.get(classOf[XmlScoverageReportParser]) 36 | 37 | def parse(reportFilePath: String, pathSanitizer: PathSanitizer): ProjectStatementCoverage = { 38 | require(reportFilePath != null) 39 | require(!reportFilePath.trim.isEmpty) 40 | 41 | log.debug(LogUtil.f("Reading report. [" + reportFilePath + "]")) 42 | 43 | val parser = new XmlScoverageReportConstructingParser(sourceFromFile(reportFilePath), pathSanitizer) 44 | parser.parse() 45 | } 46 | 47 | private def sourceFromFile(scoverageReportPath: String) = { 48 | try { 49 | Source.fromFile(scoverageReportPath) 50 | } 51 | catch { 52 | case ex: Exception => throw ScoverageException("Cannot parse file! [" + scoverageReportPath + "]", ex) 53 | } 54 | } 55 | } 56 | 57 | object XmlScoverageReportParser { 58 | def apply() = new XmlScoverageReportParser 59 | } -------------------------------------------------------------------------------- /plugin/src/test/scala/com/buransky/plugins/scoverage/pathcleaner/BruteForceSequenceMatcherSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.pathcleaner 21 | 22 | import org.junit.runner.RunWith 23 | import org.scalatest.mock.MockitoSugar 24 | import org.scalatest.junit.JUnitRunner 25 | import org.scalatest.FlatSpec 26 | import com.buransky.plugins.scoverage.pathcleaner.BruteForceSequenceMatcher.PathSeq 27 | import org.scalatest.Matchers 28 | import java.io.File 29 | import org.mockito.Mockito._ 30 | 31 | @RunWith(classOf[JUnitRunner]) 32 | class BruteForceSequenceMatcherSpec extends FlatSpec with Matchers with MockitoSugar { 33 | 34 | // file-map of all files under baseDir/sonar.sources organized by their filename 35 | val filesMap: Map[String, Seq[PathSeq]] = Map ( 36 | "rootTestFile.scala" -> List(List("testProject", "main", "rootTestFile.scala")), 37 | "nestedTestFile.scala" -> List(List("testProject", "main", "some", "folders", "nestedTestFile.scala")), 38 | "multiFile.scala" -> List( 39 | List("testProject", "main", "some", "multiFile.scala"), 40 | List("testProject", "main", "some", "folder", "multiFile.scala") 41 | ) 42 | ) 43 | 44 | // baseDir = testProject sonar.sources = main 45 | val testee = new BruteForceSequenceMatcherTestee("/testProject/main", filesMap) 46 | 47 | 48 | 49 | behavior of "BruteForceSequenceMatcher with absolute report filenames" 50 | 51 | it should "provide just the filename for top level files" in { 52 | testee.getSourceRelativePath(List("testProject", "main", "rootTestFile.scala")).get shouldEqual List("rootTestFile.scala") 53 | } 54 | 55 | it should "provide the filename and the folders for nested files" in { 56 | testee.getSourceRelativePath(List("testProject", "main", "some", "folders", "nestedTestFile.scala")).get shouldEqual List("some", "folders", "nestedTestFile.scala") 57 | } 58 | 59 | it should "find the correct file if multiple files with same name exist" in { 60 | testee.getSourceRelativePath(List("testProject", "main", "some", "multiFile.scala")).get shouldEqual List("some", "multiFile.scala") 61 | testee.getSourceRelativePath(List("testProject", "main", "some", "folder", "multiFile.scala")).get shouldEqual List("some", "folder", "multiFile.scala") 62 | } 63 | 64 | 65 | 66 | 67 | behavior of "BruteForceSequenceMatcher with filenames relative to the base dir" 68 | 69 | it should "provide just the filename for top level files" in { 70 | testee.getSourceRelativePath(List("main", "rootTestFile.scala")).get shouldEqual List("rootTestFile.scala") 71 | } 72 | 73 | it should "provide the filename and the folders for nested files" in { 74 | testee.getSourceRelativePath(List("main", "some", "folders", "nestedTestFile.scala")).get shouldEqual List("some", "folders", "nestedTestFile.scala") 75 | } 76 | 77 | it should "find the correct file if multiple files with same name exist" in { 78 | testee.getSourceRelativePath(List("main", "some", "multiFile.scala")).get shouldEqual List("some", "multiFile.scala") 79 | testee.getSourceRelativePath(List("main", "some", "folder", "multiFile.scala")).get shouldEqual List("some", "folder", "multiFile.scala") 80 | } 81 | 82 | 83 | 84 | 85 | behavior of "BruteForceSequenceMatcher with filenames relative to the src dir" 86 | 87 | it should "provide just the filename for top level files" in { 88 | testee.getSourceRelativePath(List("rootTestFile.scala")).get shouldEqual List("rootTestFile.scala") 89 | } 90 | 91 | it should "provide the filename and the folders for nested files" in { 92 | testee.getSourceRelativePath(List("some", "folders", "nestedTestFile.scala")).get shouldEqual List("some", "folders", "nestedTestFile.scala") 93 | } 94 | 95 | it should "find the correct file if multiple files with same name exist" in { 96 | testee.getSourceRelativePath(List("some", "multiFile.scala")).get shouldEqual List("some", "multiFile.scala") 97 | testee.getSourceRelativePath(List("some", "folder", "multiFile.scala")).get shouldEqual List("some", "folder", "multiFile.scala") 98 | } 99 | 100 | 101 | 102 | 103 | class BruteForceSequenceMatcherTestee(absoluteSrcPath: String, filesMap: Map[String, Seq[PathSeq]]) 104 | extends BruteForceSequenceMatcher(mock[File], "") { 105 | 106 | def srcDir = { 107 | val dir = mock[File] 108 | when(dir.isAbsolute).thenReturn(true) 109 | when(dir.isDirectory).thenReturn(true) 110 | when(dir.getAbsolutePath).thenReturn(absoluteSrcPath) 111 | dir 112 | } 113 | 114 | override private[pathcleaner] def initSourceDir(): File = srcDir 115 | override private[pathcleaner] def initFilesMap(): Map[String, Seq[PathSeq]] = filesMap 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /plugin/src/test/scala/com/buransky/plugins/scoverage/sensor/ScoverageSensorSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.sensor 21 | 22 | import java.io.File 23 | import java.util 24 | 25 | import com.buransky.plugins.scoverage.language.Scala 26 | import com.buransky.plugins.scoverage.measure.ScalaMetrics 27 | import com.buransky.plugins.scoverage.{CoveredStatement, DirectoryStatementCoverage, FileStatementCoverage, ProjectStatementCoverage, ScoverageReportParser, StatementPosition} 28 | import org.junit.runner.RunWith 29 | import org.mockito.Mockito._ 30 | import org.scalatest.junit.JUnitRunner 31 | import org.scalatest.mock.MockitoSugar 32 | import org.scalatest.{FlatSpec, Matchers} 33 | import org.sonar.api.batch.fs.{FilePredicate, FilePredicates, FileSystem, InputFile} 34 | import org.sonar.api.config.Settings 35 | import org.sonar.api.resources.Project 36 | import org.sonar.api.scan.filesystem.PathResolver 37 | 38 | import scala.collection.JavaConversions._ 39 | import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer 40 | import org.mockito.Matchers.any 41 | import org.sonar.api.measures.CoreMetrics 42 | 43 | 44 | @RunWith(classOf[JUnitRunner]) 45 | class ScoverageSensorSpec extends FlatSpec with Matchers with MockitoSugar { 46 | behavior of "shouldExecuteOnProject" 47 | 48 | it should "succeed for Scala project" in new ShouldExecuteOnProject { 49 | checkShouldExecuteOnProject(List("scala"), true) 50 | } 51 | 52 | it should "succeed for mixed projects" in new ShouldExecuteOnProject { 53 | checkShouldExecuteOnProject(List("scala", "java"), true) 54 | } 55 | 56 | it should "fail for Java project" in new ShouldExecuteOnProject { 57 | checkShouldExecuteOnProject(List("java"), false) 58 | } 59 | 60 | class ShouldExecuteOnProject extends ScoverageSensorScope { 61 | protected def checkShouldExecuteOnProject(languages: Iterable[String], expectedResult: Boolean) { 62 | // Setup 63 | val project = mock[Project] 64 | when(fileSystem.languages()).thenReturn(new util.TreeSet(languages)) 65 | 66 | // Execute & asser 67 | shouldExecuteOnProject(project) should equal(expectedResult) 68 | 69 | verify(fileSystem, times(1)).languages 70 | 71 | } 72 | } 73 | 74 | behavior of "analyse for single project" 75 | 76 | it should "set 0% coverage for a project without children" in new AnalyseScoverageSensorScope { 77 | // Setup 78 | val pathToScoverageReport = "#path-to-scoverage-report#" 79 | val reportAbsolutePath = "#report-absolute-path#" 80 | val projectStatementCoverage = 81 | ProjectStatementCoverage("project-name", List( 82 | DirectoryStatementCoverage(File.separator, List( 83 | DirectoryStatementCoverage("home", List( 84 | FileStatementCoverage("a.scala", 3, 2, Nil) 85 | )) 86 | )), 87 | DirectoryStatementCoverage("x", List( 88 | FileStatementCoverage("b.scala", 3, 2, Seq( 89 | CoveredStatement(StatementPosition(20, 1), StatementPosition(20, 2), 0, true), 90 | CoveredStatement(StatementPosition(20, 1), StatementPosition(20, 2), 1, true), 91 | CoveredStatement(StatementPosition(21, 2), StatementPosition(21, 3), 1, false) 92 | )) 93 | )) 94 | )) 95 | val reportFile = mock[java.io.File] 96 | val moduleBaseDir = mock[java.io.File] 97 | val filePredicates = mock[FilePredicates] 98 | val inputFile = mock[InputFile] 99 | 100 | when(reportFile.exists).thenReturn(true) 101 | when(reportFile.isFile).thenReturn(true) 102 | when(reportFile.getAbsolutePath).thenReturn(reportAbsolutePath) 103 | when(settings.getString(SCOVERAGE_REPORT_PATH_PROPERTY)).thenReturn(pathToScoverageReport) 104 | when(fileSystem.baseDir).thenReturn(moduleBaseDir) 105 | when(fileSystem.predicates).thenReturn(filePredicates) 106 | when(fileSystem.inputFile(any[FilePredicate]())).thenReturn(inputFile) 107 | when(inputFile.relativePath()).thenReturn("InputFile") 108 | when(pathResolver.relativeFile(moduleBaseDir, pathToScoverageReport)).thenReturn(reportFile) 109 | when(scoverageReportParser.parse(any[String](), any[PathSanitizer]())).thenReturn(projectStatementCoverage) 110 | 111 | // Execute 112 | analyse(project, context) 113 | 114 | val metricValues = Map( 115 | ScalaMetrics.statementCoverage -> 66.7, 116 | ScalaMetrics.coveredStatements -> 2.0, 117 | ScalaMetrics.totalStatements -> 3.0, 118 | CoreMetrics.LINES_TO_COVER -> 2.0, 119 | CoreMetrics.UNCOVERED_LINES -> 1.0, 120 | CoreMetrics.COVERAGE_LINE_HITS_DATA -> null, 121 | CoreMetrics.CONDITIONS_TO_COVER -> 2.0, 122 | CoreMetrics.UNCOVERED_CONDITIONS -> 1.0, 123 | CoreMetrics.CONDITIONS_BY_LINE -> null, 124 | CoreMetrics.COVERED_CONDITIONS_BY_LINE -> null 125 | ) 126 | metricValues.foreach { case (metric, metricValue) => 127 | val measure = context.getMeasure(metric) 128 | measure should not be null 129 | measure.getValue shouldBe metricValue 130 | } 131 | } 132 | 133 | class AnalyseScoverageSensorScope extends ScoverageSensorScope { 134 | val project = mock[Project] 135 | val context = new TestSensorContext 136 | 137 | override protected lazy val scoverageReportParser = mock[ScoverageReportParser] 138 | override protected def createPathSanitizer(sonarSources: String) = mock[PathSanitizer] 139 | } 140 | 141 | class ScoverageSensorScope extends { 142 | val scala = new Scala 143 | val settings = mock[Settings] 144 | val pathResolver = mock[PathResolver] 145 | val fileSystem = mock[FileSystem] 146 | } with ScoverageSensor(settings, pathResolver, fileSystem) 147 | 148 | } 149 | -------------------------------------------------------------------------------- /plugin/src/test/scala/com/buransky/plugins/scoverage/sensor/TestSensorContext.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.sensor 21 | 22 | import java.lang.Double 23 | import java.util.Date 24 | import java.{io, util} 25 | 26 | import org.sonar.api.batch.fs.{FileSystem, InputFile, InputPath} 27 | import org.sonar.api.batch.rule.ActiveRules 28 | import org.sonar.api.batch.sensor.dependency.NewDependency 29 | import org.sonar.api.batch.sensor.duplication.NewDuplication 30 | import org.sonar.api.batch.sensor.highlighting.NewHighlighting 31 | import org.sonar.api.batch.sensor.issue.NewIssue 32 | import org.sonar.api.batch.sensor.measure.NewMeasure 33 | import org.sonar.api.batch.{AnalysisMode, Event, SensorContext} 34 | import org.sonar.api.config.Settings 35 | import org.sonar.api.design.Dependency 36 | import org.sonar.api.measures.{Measure, MeasuresFilter, Metric} 37 | import org.sonar.api.resources.{File, ProjectLink, Resource} 38 | import org.sonar.api.rules.Violation 39 | 40 | import scala.collection.mutable 41 | 42 | class TestSensorContext extends SensorContext { 43 | 44 | private val measures = mutable.Map[String, Measure[_ <: io.Serializable]]() 45 | 46 | override def saveDependency(dependency: Dependency): Dependency = ??? 47 | 48 | override def isExcluded(reference: Resource): Boolean = ??? 49 | 50 | override def deleteLink(key: String): Unit = ??? 51 | 52 | override def isIndexed(reference: Resource, acceptExcluded: Boolean): Boolean = ??? 53 | 54 | override def saveViolations(violations: util.Collection[Violation]): Unit = ??? 55 | 56 | override def getParent(reference: Resource): Resource = ??? 57 | 58 | override def getOutgoingDependencies(from: Resource): util.Collection[Dependency] = ??? 59 | 60 | override def saveSource(reference: Resource, source: String): Unit = ??? 61 | 62 | override def getMeasures[M](filter: MeasuresFilter[M]): M = ??? 63 | 64 | override def getMeasures[M](resource: Resource, filter: MeasuresFilter[M]): M = ??? 65 | 66 | override def deleteEvent(event: Event): Unit = ??? 67 | 68 | override def saveViolation(violation: Violation, force: Boolean): Unit = ??? 69 | 70 | override def saveViolation(violation: Violation): Unit = ??? 71 | 72 | override def saveResource(resource: Resource): String = ??? 73 | 74 | override def getEvents(resource: Resource): util.List[Event] = ??? 75 | 76 | override def getDependencies: util.Set[Dependency] = ??? 77 | 78 | override def getIncomingDependencies(to: Resource): util.Collection[Dependency] = ??? 79 | 80 | override def index(resource: Resource): Boolean = ??? 81 | 82 | override def index(resource: Resource, parentReference: Resource): Boolean = ??? 83 | 84 | override def saveLink(link: ProjectLink): Unit = ??? 85 | 86 | override def getMeasure[G <: io.Serializable](metric: Metric[G]): Measure[G] = measures.get(metric.getKey).orNull.asInstanceOf[Measure[G]] 87 | 88 | override def getMeasure[G <: io.Serializable](resource: Resource, metric: Metric[G]): Measure[G] = ??? 89 | 90 | override def getChildren(reference: Resource): util.Collection[Resource] = ??? 91 | 92 | override def createEvent(resource: Resource, name: String, description: String, category: String, date: Date): Event = ??? 93 | 94 | override def getResource[R <: Resource](reference: R): R = ??? 95 | 96 | override def getResource(inputPath: InputPath): Resource = new File(inputPath.relativePath()) 97 | 98 | override def saveMeasure(measure: Measure[_ <: io.Serializable]): Measure[_ <: io.Serializable] = ??? 99 | 100 | override def saveMeasure(metric: Metric[_ <: io.Serializable], value: Double): Measure[_ <: io.Serializable] = ??? 101 | 102 | override def saveMeasure(resource: Resource, metric: Metric[_ <: io.Serializable], value: Double): Measure[_ <: io.Serializable] = ??? 103 | 104 | override def saveMeasure(resource: Resource, measure: Measure[_ <: io.Serializable]): Measure[_ <: io.Serializable] = { 105 | measures.put(measure.getMetricKey, measure) 106 | measure 107 | } 108 | 109 | override def saveMeasure(inputFile: InputFile, metric: Metric[_ <: io.Serializable], value: Double): Measure[_ <: io.Serializable] = ??? 110 | 111 | override def saveMeasure(inputFile: InputFile, measure: Measure[_ <: io.Serializable]): Measure[_ <: io.Serializable] = ??? 112 | 113 | override def newDuplication(): NewDuplication = ??? 114 | 115 | override def activeRules(): ActiveRules = ??? 116 | 117 | override def newHighlighting(): NewHighlighting = ??? 118 | 119 | override def analysisMode(): AnalysisMode = ??? 120 | 121 | override def fileSystem(): FileSystem = ??? 122 | 123 | override def newDependency(): NewDependency = ??? 124 | 125 | override def settings(): Settings = ??? 126 | 127 | override def newMeasure[G <: io.Serializable](): NewMeasure[G] = ??? 128 | 129 | override def newIssue(): NewIssue = ??? 130 | } -------------------------------------------------------------------------------- /plugin/src/test/scala/com/buransky/plugins/scoverage/util/PathUtilSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.util 21 | 22 | import org.scalatest.{FlatSpec, Matchers} 23 | import org.junit.runner.RunWith 24 | import org.scalatest.junit.JUnitRunner 25 | 26 | @RunWith(classOf[JUnitRunner]) 27 | class PathUtilSpec extends FlatSpec with Matchers { 28 | 29 | val osName = System.getProperty("os.name") 30 | val separator = System.getProperty("file.separator") 31 | 32 | behavior of s"splitPath for $osName" 33 | 34 | it should "ignore the empty path" in { 35 | PathUtil.splitPath("") should equal(List.empty[String]) 36 | } 37 | 38 | it should "ignore a separator at the beginning" in { 39 | PathUtil.splitPath(s"${separator}a") should equal(List("a")) 40 | } 41 | 42 | it should "work with separator in the middle" in { 43 | PathUtil.splitPath(s"a${separator}b") should equal(List("a", "b")) 44 | } 45 | 46 | it should "work with an OS dependent absolute path" in { 47 | if (osName.startsWith("Windows")) { 48 | PathUtil.splitPath("C:\\test\\2") should equal(List("test", "2")) 49 | } else { 50 | PathUtil.splitPath("/test/2") should equal(List("test", "2")) 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportConstructingParserSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.xml 21 | 22 | import org.junit.runner.RunWith 23 | import org.scalatest.junit.JUnitRunner 24 | import org.scalatest.{Matchers, FlatSpec} 25 | import scala.io.Source 26 | import com.buransky.plugins.scoverage.xml.data.XmlReportFile1 27 | import scala._ 28 | import com.buransky.plugins.scoverage.{ProjectStatementCoverage, FileStatementCoverage, DirectoryStatementCoverage} 29 | import com.buransky.plugins.scoverage.pathcleaner.PathSanitizer 30 | import com.buransky.plugins.scoverage.StatementCoverage 31 | import com.buransky.plugins.scoverage.NodeStatementCoverage 32 | 33 | @RunWith(classOf[JUnitRunner]) 34 | class XmlScoverageReportConstructingParserSpec extends FlatSpec with Matchers { 35 | behavior of "parse source" 36 | 37 | it must "parse old broken Scoverage 0.95 file correctly" in { 38 | val sanitizer = new PathSanitizer() { 39 | def getSourceRelativePath(path: Seq[String]): Option[Seq[String]] = { 40 | // do nothing 41 | Some(path) 42 | } 43 | } 44 | assertReportFile(XmlReportFile1.scoverage095Data, 24.53, sanitizer)(assertScoverage095Data) 45 | } 46 | 47 | it must "parse new fixed Scoverage 1.0.4 file correctly" in { 48 | val sanitizer = new PathSanitizer() { 49 | def getSourceRelativePath(path: Seq[String]): Option[Seq[String]] = { 50 | // drop first 6 = /a1b2c3/workspace/sonar-test/src/main/scala 51 | Some(path.drop(6)) 52 | } 53 | } 54 | assertReportFile(XmlReportFile1.scoverage104Data, 33.33, sanitizer) { projectCoverage => 55 | assert(projectCoverage.name === "") 56 | assert(projectCoverage.children.size === 1) 57 | 58 | projectCoverage.children.head match { 59 | case rootDir: DirectoryStatementCoverage => { 60 | val rr = checkNode(rootDir, "com", 0, 0, 0.0, 0d).head 61 | val test = checkNode(rr, "rr", 0, 0, 0.0, 0d).head 62 | val sonar = checkNode(test, "test", 0, 0, 0.0, 0d).head 63 | val mainClass = checkNode(sonar, "sonar", 3, 1, 33.33, 50.0).head 64 | 65 | checkNode(mainClass, "MainClass.scala", 3, 1, 33.33, 50.0) 66 | } 67 | case other => fail(s"This is not a directory statement coverage! [$other]") 68 | } 69 | } 70 | } 71 | 72 | it must "parse file1 correctly even without XML declaration" in { 73 | val sanitizer = new PathSanitizer() { 74 | def getSourceRelativePath(path: Seq[String]): Option[Seq[String]] = { 75 | // do nothing 76 | Some(path) 77 | } 78 | } 79 | assertReportFile(XmlReportFile1.dataWithoutDeclaration, 24.53, sanitizer)(assertScoverage095Data) 80 | } 81 | 82 | private def assertReportFile(data: String, expectedCoverage: Double, pathSanitizer: PathSanitizer)(f: (ProjectStatementCoverage) => Unit) { 83 | val parser = new XmlScoverageReportConstructingParser(Source.fromString(data), pathSanitizer) 84 | val projectCoverage = parser.parse() 85 | 86 | // Assert coverage 87 | checkRate(expectedCoverage, projectCoverage.rate) 88 | 89 | f(projectCoverage) 90 | } 91 | 92 | private def assertScoverage095Data(projectCoverage: ProjectStatementCoverage): Unit = { 93 | // Assert structure 94 | projectCoverage.name should equal("") 95 | 96 | val projectChildren = projectCoverage.children.toList 97 | projectChildren.length should equal(1) 98 | projectChildren.head shouldBe a [DirectoryStatementCoverage] 99 | 100 | val aaa = projectChildren.head.asInstanceOf[DirectoryStatementCoverage] 101 | aaa.name should equal("aaa") 102 | checkRate(24.53, aaa.rate) 103 | 104 | val aaaChildren = aaa.children.toList.sortBy(_.statementCount) 105 | aaaChildren.length should equal(2) 106 | 107 | aaaChildren(1) shouldBe a [FileStatementCoverage] 108 | val errorCode = aaaChildren(1).asInstanceOf[FileStatementCoverage] 109 | errorCode.name should equal("ErrorCode.scala") 110 | errorCode.statementCount should equal (46) 111 | errorCode.coveredStatementsCount should equal (13) 112 | 113 | aaaChildren.head shouldBe a [FileStatementCoverage] 114 | val graph = aaaChildren.head.asInstanceOf[FileStatementCoverage] 115 | graph.name should equal("Graph.scala") 116 | graph.statementCount should equal (7) 117 | graph.coveredStatementsCount should equal (0) 118 | } 119 | 120 | private def checkRate(expected: Double, real: Double) { 121 | BigDecimal(real).setScale(2, BigDecimal.RoundingMode.HALF_UP).should(equal(BigDecimal(expected))) 122 | } 123 | 124 | private def checkNode(node: NodeStatementCoverage, name: String, count: Int, covered: Int, rate: Double, branchRate: Double) = { 125 | node.name shouldEqual name 126 | node.statementCount shouldEqual count 127 | node.coveredStatementsCount shouldEqual covered 128 | checkRate(branchRate, node.branchRate) 129 | checkRate(rate, node.rate) 130 | 131 | node.children 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /plugin/src/test/scala/com/buransky/plugins/scoverage/xml/XmlScoverageReportParserSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.xml 21 | 22 | import org.scalatest.{FlatSpec, Matchers} 23 | import org.scalatest.junit.JUnitRunner 24 | import org.junit.runner.RunWith 25 | import com.buransky.plugins.scoverage.ScoverageException 26 | 27 | @RunWith(classOf[JUnitRunner]) 28 | class XmlScoverageReportParserSpec extends FlatSpec with Matchers { 29 | behavior of "parse file path" 30 | 31 | it must "fail for null path" in { 32 | the[IllegalArgumentException] thrownBy XmlScoverageReportParser().parse(null.asInstanceOf[String], null) 33 | } 34 | 35 | it must "fail for empty path" in { 36 | the[IllegalArgumentException] thrownBy XmlScoverageReportParser().parse("", null) 37 | } 38 | 39 | it must "fail for not existing path" in { 40 | the[ScoverageException] thrownBy XmlScoverageReportParser().parse("/x/a/b/c/1/2/3/4.xml", null) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugin/src/test/scala/com/buransky/plugins/scoverage/xml/data/XmlReportFile1.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Scoverage Plugin 3 | * Copyright (C) 2013 Rado Buransky 4 | * dev@sonar.codehaus.org 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package com.buransky.plugins.scoverage.xml.data 21 | 22 | object XmlReportFile1 { 23 | val scoverage104Data = 24 | """ 25 | | 27 | | 28 | | 29 | | 30 | | 32 | | 33 | | 35 | | 36 | | 38 | | 39 | | 41 | | 42 | | | 43 | | 44 | | 46 | | 47 | | 49 | | 50 | | 51 | | 52 | | 53 | | 54 | | 55 | | 56 | | 57 | | 58 | |""".stripMargin 59 | 60 | val scoverage095Data = 61 | """ 62 | | 63 | | 64 | | 65 | | 66 | | 67 | | 68 | | 69 | | 70 | | 72 | | MyServiceClientError.this.error("zipcodeinvalid") 73 | | 74 | | 75 | | 76 | | 77 | | 78 | | 79 | | 80 | | 81 | | 82 | | 84 | | 2 85 | | 86 | | 88 | | 3 89 | | 90 | | 92 | | scala.Some.apply[String]("One") 93 | | 94 | | 96 | | new $anon() 97 | | 98 | | 99 | | 100 | | 101 | | 102 | | 103 | | 104 | | 105 | | 106 | | 108 | | MyServiceLogicError.this.error("logicfailed") 109 | | 110 | | 111 | | 112 | | 113 | | 114 | | 115 | | 116 | | 117 | | 118 | | 120 | | StructuredErrorCode.this.parent.toString() 121 | | 122 | | 124 | | p.==("") 125 | | 126 | | 128 | | "" 129 | | 130 | | 132 | | { 133 | | scoverage.Invoker.invoked(8, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 134 | | "" 135 | |} 136 | | 137 | | 139 | | p.+("-") 140 | | 141 | | 143 | | { 144 | | scoverage.Invoker.invoked(10, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 145 | | p.+("-") 146 | |} 147 | | 148 | | 150 | | StructuredErrorCode.this.name 151 | | 152 | | 154 | | if ({ 155 | | scoverage.Invoker.invoked(7, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 156 | | p.==("") 157 | |}) 158 | | { 159 | | scoverage.Invoker.invoked(9, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 160 | | { 161 | | scoverage.Invoker.invoked(8, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 162 | | "" 163 | | } 164 | | } 165 | |else 166 | | { 167 | | scoverage.Invoker.invoked(11, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 168 | | { 169 | | scoverage.Invoker.invoked(10, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 170 | | p.+("-") 171 | | } 172 | | }.+({ 173 | | scoverage.Invoker.invoked(12, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 174 | | StructuredErrorCode.this.name 175 | |}) 176 | | 177 | | 178 | | 179 | | 180 | | 181 | | 183 | | errorCode.==(this) 184 | | 185 | | 187 | | true 188 | | 189 | | 191 | | { 192 | | scoverage.Invoker.invoked(2, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 193 | | true 194 | |} 195 | | 196 | | 198 | | StructuredErrorCode.this.parent.is(errorCode) 199 | | 200 | | 202 | | { 203 | | scoverage.Invoker.invoked(4, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 204 | | StructuredErrorCode.this.parent.is(errorCode) 205 | |} 206 | | 207 | | 208 | | 209 | | 210 | | 211 | | 213 | | StructuredErrorCode.apply(name, this) 214 | | 215 | | 216 | | 217 | | 218 | | 219 | | 220 | | 221 | | 222 | | 223 | | 225 | | ClientError.required 226 | | 227 | | 229 | | scala.this.Predef.println({ 230 | | scoverage.Invoker.invoked(25, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 231 | | ClientError.required 232 | |}) 233 | | 234 | | 236 | | ClientError.invalid 237 | | 238 | | 240 | | scala.this.Predef.println({ 241 | | scoverage.Invoker.invoked(27, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 242 | | ClientError.invalid 243 | |}) 244 | | 245 | | 247 | | scala.this.Predef.println(MySqlError) 248 | | 249 | | 251 | | MySqlError.syntax 252 | | 253 | | 255 | | scala.this.Predef.println({ 256 | | scoverage.Invoker.invoked(30, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 257 | | MySqlError.syntax 258 | |}) 259 | | 260 | | 262 | | MyServiceLogicError.logicFailed 263 | | 264 | | 266 | | scala.this.Predef.println({ 267 | | scoverage.Invoker.invoked(32, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 268 | | MyServiceLogicError.logicFailed 269 | |}) 270 | | 271 | | 273 | | ClientError.required 274 | | 275 | | 277 | | e 278 | | 279 | | 281 | | scala.this.Predef.println("required") 282 | | 283 | | 285 | | { 286 | | scoverage.Invoker.invoked(36, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 287 | | scala.this.Predef.println("required") 288 | |} 289 | | 290 | | 292 | | scala.this.Predef.println("invalid") 293 | | 294 | | 296 | | { 297 | | scoverage.Invoker.invoked(38, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 298 | | scala.this.Predef.println("invalid") 299 | |} 300 | | 301 | | 303 | | () 304 | | 305 | | 307 | | { 308 | | scoverage.Invoker.invoked(40, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 309 | | () 310 | |} 311 | | 312 | | 314 | | MyServiceServerError.mongoDbError.is(ServerError) 315 | | 316 | | 318 | | scala.this.Predef.println("This is a server error") 319 | | 320 | | 322 | | { 323 | | scoverage.Invoker.invoked(43, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 324 | | scala.this.Predef.println("This is a server error") 325 | |} 326 | | 327 | | 329 | | () 330 | | 331 | | 333 | | { 334 | | scoverage.Invoker.invoked(45, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 335 | | () 336 | |} 337 | | 338 | | 339 | | 340 | | 341 | | 342 | | 343 | | 344 | | 345 | | 346 | | 348 | | MySqlError.this.error("syntax") 349 | | 350 | | 352 | | MySqlError.this.error("connection") 353 | | 354 | | 355 | | 356 | | 357 | | 358 | | 359 | | 360 | | 361 | | 362 | | 364 | | MyServiceServerError.this.error("mongodberror") 365 | | 366 | | 367 | | 368 | | 369 | | 370 | | 371 | | 372 | | 373 | | 374 | | 376 | | "" 377 | | 378 | | 379 | | 380 | | 381 | | 382 | | 384 | | false 385 | | 386 | | 387 | | 388 | | 389 | | 390 | | 391 | | 392 | | 393 | | 394 | | 396 | | ServerError.this.error("solar") 397 | | 398 | | 399 | | 400 | | 401 | | 402 | | 403 | | 404 | | 405 | | 406 | | 408 | | ClientError.this.error("required") 409 | | 410 | | 412 | | ClientError.this.error("invalid") 413 | | 414 | | 415 | | 416 | | 417 | | 418 | | 419 | | 420 | | 421 | | 422 | | 423 | | 424 | | 425 | | 426 | | 428 | | aaa.MakeRectangleModelFromFile.apply(null) 429 | | 430 | | 432 | | x.isInstanceOf[Serializable] 433 | | 434 | | 436 | | scala.this.Predef.println({ 437 | | scoverage.Invoker.invoked(52, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 438 | | x.isInstanceOf[Serializable] 439 | |}) 440 | | 441 | | 442 | | 443 | | 444 | | 445 | | 446 | | 447 | | 448 | | 449 | """.stripMargin 450 | 451 | val dataWithoutDeclaration = 452 | """ 453 | | 454 | | 455 | | 456 | | 457 | | 458 | | 459 | | 460 | | 462 | | MyServiceClientError.this.error("zipcodeinvalid") 463 | | 464 | | 465 | | 466 | | 467 | | 468 | | 469 | | 470 | | 471 | | 472 | | 474 | | 2 475 | | 476 | | 478 | | 3 479 | | 480 | | 482 | | scala.Some.apply[String]("One") 483 | | 484 | | 486 | | new $anon() 487 | | 488 | | 489 | | 490 | | 491 | | 492 | | 493 | | 494 | | 495 | | 496 | | 498 | | MyServiceLogicError.this.error("logicfailed") 499 | | 500 | | 501 | | 502 | | 503 | | 504 | | 505 | | 506 | | 507 | | 508 | | 510 | | StructuredErrorCode.this.parent.toString() 511 | | 512 | | 514 | | p.==("") 515 | | 516 | | 518 | | "" 519 | | 520 | | 522 | | { 523 | | scoverage.Invoker.invoked(8, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 524 | | "" 525 | |} 526 | | 527 | | 529 | | p.+("-") 530 | | 531 | | 533 | | { 534 | | scoverage.Invoker.invoked(10, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 535 | | p.+("-") 536 | |} 537 | | 538 | | 540 | | StructuredErrorCode.this.name 541 | | 542 | | 544 | | if ({ 545 | | scoverage.Invoker.invoked(7, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 546 | | p.==("") 547 | |}) 548 | | { 549 | | scoverage.Invoker.invoked(9, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 550 | | { 551 | | scoverage.Invoker.invoked(8, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 552 | | "" 553 | | } 554 | | } 555 | |else 556 | | { 557 | | scoverage.Invoker.invoked(11, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 558 | | { 559 | | scoverage.Invoker.invoked(10, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 560 | | p.+("-") 561 | | } 562 | | }.+({ 563 | | scoverage.Invoker.invoked(12, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 564 | | StructuredErrorCode.this.name 565 | |}) 566 | | 567 | | 568 | | 569 | | 570 | | 571 | | 573 | | errorCode.==(this) 574 | | 575 | | 577 | | true 578 | | 579 | | 581 | | { 582 | | scoverage.Invoker.invoked(2, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 583 | | true 584 | |} 585 | | 586 | | 588 | | StructuredErrorCode.this.parent.is(errorCode) 589 | | 590 | | 592 | | { 593 | | scoverage.Invoker.invoked(4, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 594 | | StructuredErrorCode.this.parent.is(errorCode) 595 | |} 596 | | 597 | | 598 | | 599 | | 600 | | 601 | | 603 | | StructuredErrorCode.apply(name, this) 604 | | 605 | | 606 | | 607 | | 608 | | 609 | | 610 | | 611 | | 612 | | 613 | | 615 | | ClientError.required 616 | | 617 | | 619 | | scala.this.Predef.println({ 620 | | scoverage.Invoker.invoked(25, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 621 | | ClientError.required 622 | |}) 623 | | 624 | | 626 | | ClientError.invalid 627 | | 628 | | 630 | | scala.this.Predef.println({ 631 | | scoverage.Invoker.invoked(27, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 632 | | ClientError.invalid 633 | |}) 634 | | 635 | | 637 | | scala.this.Predef.println(MySqlError) 638 | | 639 | | 641 | | MySqlError.syntax 642 | | 643 | | 645 | | scala.this.Predef.println({ 646 | | scoverage.Invoker.invoked(30, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 647 | | MySqlError.syntax 648 | |}) 649 | | 650 | | 652 | | MyServiceLogicError.logicFailed 653 | | 654 | | 656 | | scala.this.Predef.println({ 657 | | scoverage.Invoker.invoked(32, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 658 | | MyServiceLogicError.logicFailed 659 | |}) 660 | | 661 | | 663 | | ClientError.required 664 | | 665 | | 667 | | e 668 | | 669 | | 671 | | scala.this.Predef.println("required") 672 | | 673 | | 675 | | { 676 | | scoverage.Invoker.invoked(36, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 677 | | scala.this.Predef.println("required") 678 | |} 679 | | 680 | | 682 | | scala.this.Predef.println("invalid") 683 | | 684 | | 686 | | { 687 | | scoverage.Invoker.invoked(38, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 688 | | scala.this.Predef.println("invalid") 689 | |} 690 | | 691 | | 693 | | () 694 | | 695 | | 697 | | { 698 | | scoverage.Invoker.invoked(40, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 699 | | () 700 | |} 701 | | 702 | | 704 | | MyServiceServerError.mongoDbError.is(ServerError) 705 | | 706 | | 708 | | scala.this.Predef.println("This is a server error") 709 | | 710 | | 712 | | { 713 | | scoverage.Invoker.invoked(43, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 714 | | scala.this.Predef.println("This is a server error") 715 | |} 716 | | 717 | | 719 | | () 720 | | 721 | | 723 | | { 724 | | scoverage.Invoker.invoked(45, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 725 | | () 726 | |} 727 | | 728 | | 729 | | 730 | | 731 | | 732 | | 733 | | 734 | | 735 | | 736 | | 738 | | MySqlError.this.error("syntax") 739 | | 740 | | 742 | | MySqlError.this.error("connection") 743 | | 744 | | 745 | | 746 | | 747 | | 748 | | 749 | | 750 | | 751 | | 752 | | 754 | | MyServiceServerError.this.error("mongodberror") 755 | | 756 | | 757 | | 758 | | 759 | | 760 | | 761 | | 762 | | 763 | | 764 | | 766 | | "" 767 | | 768 | | 769 | | 770 | | 771 | | 772 | | 774 | | false 775 | | 776 | | 777 | | 778 | | 779 | | 780 | | 781 | | 782 | | 783 | | 784 | | 786 | | ServerError.this.error("solar") 787 | | 788 | | 789 | | 790 | | 791 | | 792 | | 793 | | 794 | | 795 | | 796 | | 798 | | ClientError.this.error("required") 799 | | 800 | | 802 | | ClientError.this.error("invalid") 803 | | 804 | | 805 | | 806 | | 807 | | 808 | | 809 | | 810 | | 811 | | 812 | | 813 | | 814 | | 815 | | 816 | | 818 | | aaa.MakeRectangleModelFromFile.apply(null) 819 | | 820 | | 822 | | x.isInstanceOf[Serializable] 823 | | 824 | | 826 | | scala.this.Predef.println({ 827 | | scoverage.Invoker.invoked(52, "/a1b2c3/workspace/aaa/target/scala-2.10/scoverage.measurement"); 828 | | x.isInstanceOf[Serializable] 829 | |}) 830 | | 831 | | 832 | | 833 | | 834 | | 835 | | 836 | | 837 | | 838 | | 839 | """.stripMargin 840 | } 841 | -------------------------------------------------------------------------------- /samples/maven/.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # Package files 5 | *.war 6 | *.jar 7 | *.ear 8 | 9 | # Maven 10 | out/ 11 | target/ 12 | 13 | # Eclipse 14 | .project 15 | .classpath 16 | .settings/ 17 | 18 | # IDEA 19 | *.iml 20 | .idea 21 | 22 | # OSX 23 | .DS_STORE 24 | .Trashes 25 | 26 | # Windows 27 | Desktop.ini 28 | Thumbs.db 29 | 30 | # Python 31 | *.pyc 32 | 33 | -------------------------------------------------------------------------------- /samples/maven/README: -------------------------------------------------------------------------------- 1 | Run with: 2 | 3 | `mvn clean scoverage:report sonar:sonar` -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/module1/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | combined-scala-java-multi-module-sonar 6 | test 7 | 1.0.0 8 | 9 | module1 10 | jar 11 | 1.0.0 12 | 13 | -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/module1/src/main/java/module1/HelloWorld.java: -------------------------------------------------------------------------------- 1 | package module1; 2 | 3 | /** 4 | * Created by tim on 01/05/15. 5 | */ 6 | public class HelloWorld { 7 | 8 | public String hello() { 9 | return "Hello"; 10 | } 11 | 12 | public void notCovered() { 13 | System.out.println("YOLO"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/module1/src/main/scala/module1/HelloScala.scala: -------------------------------------------------------------------------------- 1 | package module1 2 | 3 | class HelloScala { 4 | 5 | case class TryOut(some: String, fields: List[String]) 6 | 7 | def test = "Hello" 8 | 9 | def someOther = 42 10 | } -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/module1/src/test/java/module1/HelloWorldTest.java: -------------------------------------------------------------------------------- 1 | package module1; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | public class HelloWorldTest { 6 | 7 | @org.junit.Test 8 | public void testHello() throws Exception { 9 | assertEquals("Hello", new HelloWorld().hello()); 10 | } 11 | } -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/module1/src/test/scala/HelloScalaTest.scala: -------------------------------------------------------------------------------- 1 | import org.junit.runner.RunWith 2 | import org.scalatest.junit.JUnitRunner 3 | import org.scalatest.{FlatSpec, ShouldMatchers} 4 | import module1.HelloScala 5 | 6 | @RunWith(classOf[JUnitRunner]) 7 | class HelloScalaTest extends FlatSpec with ShouldMatchers { 8 | 9 | "it" should "work" in { 10 | val scala: HelloScala = new HelloScala() 11 | scala.test should equal("Hello") 12 | 13 | scala.TryOut("String", List()) should not equal(true) 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/module2/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | combined-scala-java-multi-module-sonar 6 | test 7 | 1.0.0 8 | 9 | module2 10 | jar 11 | 1.0.0 12 | 13 | -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/module2/src/main/java/module2/HelloWorld2.java: -------------------------------------------------------------------------------- 1 | package module2; 2 | 3 | /** 4 | * Created by tim on 01/05/15. 5 | */ 6 | public class HelloWorld2 { 7 | 8 | public String hello() { 9 | return "Hello"; 10 | } 11 | 12 | public void notCovered() { 13 | System.out.println("YOLO"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/module2/src/main/scala/module2/HelloScala2.scala: -------------------------------------------------------------------------------- 1 | package module2 2 | 3 | class HelloScala2 { 4 | 5 | case class TryOut(some: String, fields: List[String]) 6 | 7 | def test = "Hello" 8 | 9 | def someOther = 42 10 | } -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/module2/src/test/java/HelloWorld2Test.java: -------------------------------------------------------------------------------- 1 | import module2.HelloWorld2; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | public class HelloWorld2Test { 6 | 7 | @org.junit.Test 8 | public void testHello() throws Exception { 9 | assertEquals("Hello", new HelloWorld2().hello()); 10 | } 11 | } -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/module2/src/test/scala/HelloScala2Test.scala: -------------------------------------------------------------------------------- 1 | import module2.HelloScala2 2 | import org.junit.runner.RunWith 3 | import org.scalatest.junit.JUnitRunner 4 | import org.scalatest.{FlatSpec, ShouldMatchers} 5 | 6 | @RunWith(classOf[JUnitRunner]) 7 | class HelloScala2Test extends FlatSpec with ShouldMatchers { 8 | 9 | "it" should "work" in { 10 | val scala: HelloScala2 = new HelloScala2() 11 | scala.test should equal("Hello") 12 | 13 | scala.TryOut("String", List()) should not equal(true) 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-multi-module-sonar/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | test 6 | combined-scala-java-multi-module-sonar 7 | pom 8 | 1.0.0 9 | 10 | 11 | module1 12 | module2 13 | 14 | 15 | 16 | 2.11.6 17 | 0.13.8 18 | 19 | scoverage 20 | jacoco 21 | target/surefire-reports 22 | target/scoverage.xml 23 | src 24 | target/jacoco.exec 25 | src/test/** 26 | UTF-8 27 | 28 | 29 | 30 | 31 | 32 | 33 | com.google.code.sbt-compiler-maven-plugin 34 | sbt-compiler-maven-plugin 35 | 1.0.0-beta5 36 | 37 | 38 | 39 | compile 40 | testCompile 41 | addScalaSources 42 | 43 | default-sbt-compile 44 | 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-compiler-plugin 50 | 3.2 51 | 52 | true 53 | true 54 | 55 | 56 | 57 | 58 | org.jacoco 59 | jacoco-maven-plugin 60 | 0.7.4.201502262128 61 | 62 | 63 | pre-test 64 | 65 | prepare-agent 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.scoverage 73 | scoverage-maven-plugin 74 | 1.0.4 75 | 76 | true 77 | 78 | 79 | 80 | instrument 81 | 82 | 83 | pre-compile 84 | 85 | post-compile 86 | 87 | 88 | 89 | scoverage-report 90 | 91 | 92 | report-only 93 | 94 | prepare-package 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | org.scalatest 104 | scalatest_2.11 105 | 2.2.1 106 | test 107 | 108 | 109 | org.mockito 110 | mockito-all 111 | 1.9.5 112 | test 113 | 114 | 115 | junit 116 | junit 117 | 4.11 118 | 119 | 120 | 121 | ch.qos.logback 122 | logback-classic 123 | 1.0.13 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-sonar/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | test 6 | combined-scala-java-sonar-single-module 7 | jar 8 | 1.0.0 9 | 10 | 11 | 2.11.6 12 | 13 | scoverage 14 | jacoco 15 | target/surefire-reports 16 | target/scoverage.xml 17 | target/notthere.xml 18 | src 19 | target/jacoco.exec 20 | src/test/java/**,src/test/scala/** 21 | UTF-8 22 | 23 | 24 | 25 | 26 | 27 | 28 | com.google.code.sbt-compiler-maven-plugin 29 | sbt-compiler-maven-plugin 30 | 1.0.0-beta5 31 | 32 | 33 | 34 | compile 35 | testCompile 36 | addScalaSources 37 | 38 | default-sbt-compile 39 | 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-compiler-plugin 45 | 3.2 46 | 47 | true 48 | true 49 | 50 | 51 | 52 | 53 | org.jacoco 54 | jacoco-maven-plugin 55 | 0.7.4.201502262128 56 | 57 | 58 | pre-test 59 | 60 | prepare-agent 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.scoverage 68 | scoverage-maven-plugin 69 | 1.0.4 70 | 71 | true 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.scalatest 80 | scalatest_2.11 81 | 2.2.1 82 | test 83 | 84 | 85 | org.mockito 86 | mockito-all 87 | 1.9.5 88 | test 89 | 90 | 91 | junit 92 | junit 93 | 4.11 94 | 95 | 96 | 97 | ch.qos.logback 98 | logback-classic 99 | 1.0.13 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-sonar/src/main/java/module1/HelloWorld.java: -------------------------------------------------------------------------------- 1 | package module1; 2 | 3 | /** 4 | * Created by tim on 01/05/15. 5 | */ 6 | public class HelloWorld { 7 | 8 | public String hello() { 9 | return "Hello"; 10 | } 11 | 12 | public void notCovered() { 13 | System.out.println("YOLO"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-sonar/src/main/scala/module1/HelloScala.scala: -------------------------------------------------------------------------------- 1 | package module1 2 | 3 | class HelloScala { 4 | 5 | case class TryOut(some: String, fields: List[String]) 6 | 7 | def test = "Hello" 8 | 9 | def someOther = 42 10 | } -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-sonar/src/test/java/module1/HelloWorldTest.java: -------------------------------------------------------------------------------- 1 | package module1; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | public class HelloWorldTest { 6 | 7 | @org.junit.Test 8 | public void testHello() throws Exception { 9 | assertEquals("Hello", new HelloWorld().hello()); 10 | } 11 | } -------------------------------------------------------------------------------- /samples/maven/combined-scala-java-sonar/src/test/scala/HelloScalaTest.scala: -------------------------------------------------------------------------------- 1 | import org.junit.runner.RunWith 2 | import org.scalatest.junit.JUnitRunner 3 | import org.scalatest.{FlatSpec, ShouldMatchers} 4 | import module1.HelloScala 5 | 6 | @RunWith(classOf[JUnitRunner]) 7 | class HelloScalaTest extends FlatSpec with ShouldMatchers { 8 | 9 | "it" should "work" in { 10 | val scala: HelloScala = new HelloScala() 11 | scala.test should equal("Hello") 12 | 13 | scala.TryOut("String", List()) should not equal(true) 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /samples/sbt/multi-module/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea_modules 3 | target -------------------------------------------------------------------------------- /samples/sbt/multi-module/README.md: -------------------------------------------------------------------------------- 1 | # Multi-module SBT sample project for Sonar Scoverage plugin # 2 | 3 | 1. Create quality profile for Scala language and set it to be used by default. 4 | 5 | 2. Run scoverage to generate coverage reports: 6 | 7 | $ sbt clean coverage test 8 | 9 | 3. And then run Sonar runner to upload data from reports to the Sonar server: 10 | 11 | $ sonar-runner 12 | 13 | ## Requirements ## 14 | 15 | - Installed Sonar Scoverage plugin 16 | - Installed SBT 17 | - Installed Sonar runner -------------------------------------------------------------------------------- /samples/sbt/multi-module/build.sbt: -------------------------------------------------------------------------------- 1 | organization in ThisBuild := "com.buransky" 2 | 3 | scalaVersion in ThisBuild := "2.11.6" 4 | 5 | version in ThisBuild := "5.1.0" 6 | 7 | lazy val module1 = project 8 | 9 | lazy val module2 = project 10 | -------------------------------------------------------------------------------- /samples/sbt/multi-module/module1/build.sbt: -------------------------------------------------------------------------------- 1 | name := Common.baseName + "-module1" 2 | 3 | libraryDependencies += Common.scalatest -------------------------------------------------------------------------------- /samples/sbt/multi-module/module1/src/main/scala/com/buransky/plugins/scoverage/samples/sbt/multiModule/module1/Beer.scala: -------------------------------------------------------------------------------- 1 | package com.buransky.plugins.scoverage.samples.sbt.multiModule.module1 2 | 3 | import scala.util.Random 4 | 5 | trait Beer { 6 | val volume: Double 7 | def isGood: Boolean = (volume > 0.0) 8 | } 9 | 10 | case object EmptyBeer extends { 11 | val volume = 0.0 12 | } with Beer 13 | 14 | trait SlovakBeer extends Beer { 15 | override def isGood = Random.nextBoolean 16 | } 17 | 18 | trait BelgianBeer extends Beer { 19 | if (volume > 0.25) 20 | throw new IllegalArgumentException("Too big beer for belgian beer!") 21 | 22 | override def isGood = true 23 | } 24 | 25 | case class HordonBeer(volume: Double) extends SlovakBeer { 26 | override def isGood = false 27 | } 28 | 29 | case class ChimayBeer(volume: Double) extends BelgianBeer 30 | -------------------------------------------------------------------------------- /samples/sbt/multi-module/module1/src/main/scala/com/buransky/plugins/scoverage/samples/sbt/multiModule/module1/Pub.scala: -------------------------------------------------------------------------------- 1 | package com.buransky.plugins.scoverage.samples.sbt.multiModule.module1 2 | 3 | trait Pub { 4 | def offer: Iterable[_ <: Beer] 5 | def giveMeGreat: Beer 6 | } 7 | 8 | object Delirium extends Pub { 9 | def offer = List(HordonBeer(0.5), ChimayBeer(0.2)) 10 | def giveMeGreat = offer.filter(_.isGood).filter(_.volume > 0.3).head 11 | } -------------------------------------------------------------------------------- /samples/sbt/multi-module/module1/src/test/scala/com/buransky/plugins/scoverage/samples/sbt/multiModule/module1/BeerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.buransky.plugins.scoverage.samples.sbt.multiModule.module1 2 | 3 | import org.scalatest.{Matchers, FlatSpec} 4 | 5 | class BeerSpec extends FlatSpec with Matchers { 6 | behavior of "Beer" 7 | 8 | "isGood" must "be true if not empty" in { 9 | val beer = new Beer { val volume = 0.1 } 10 | beer.isGood should equal(true) 11 | } 12 | 13 | behavior of "EmptyBeer" 14 | 15 | it must "be empty" in { 16 | EmptyBeer.volume should equal(0.0) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/sbt/multi-module/module1/src/test/scala/com/buransky/plugins/scoverage/samples/sbt/multiModule/module1/PubSpec.scala: -------------------------------------------------------------------------------- 1 | package com.buransky.plugins.scoverage.samples.sbt.multiModule.module1 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | 5 | class PubSpec extends FlatSpec with Matchers { 6 | behavior of "Delirium" 7 | 8 | it must "give me what I want" in { 9 | the[NoSuchElementException] thrownBy Delirium.giveMeGreat 10 | } 11 | } -------------------------------------------------------------------------------- /samples/sbt/multi-module/module2/build.sbt: -------------------------------------------------------------------------------- 1 | name := Common.baseName + "-module2" 2 | 3 | libraryDependencies += Common.scalatest -------------------------------------------------------------------------------- /samples/sbt/multi-module/module2/src/main/scala/com/buransky/plugins/scoverage/samples/sbt/multiModule/module2/Animal.scala: -------------------------------------------------------------------------------- 1 | package com.buransky.plugins.scoverage.samples.sbt.multiModule.module2 2 | 3 | trait Animal { 4 | val legs: Int 5 | val eyes: Int 6 | val canFly: Boolean 7 | val canSwim: Boolean 8 | } 9 | 10 | object Animal { 11 | def fancy(farm: Iterable[Animal]): Iterable[Animal] = 12 | farm.filter(_.legs > 10).filter(_.canFly).filter(_.canSwim) 13 | } -------------------------------------------------------------------------------- /samples/sbt/multi-module/module2/src/test/scala/com/buransky/plugins/scoverage/samples/sbt/multiModule/module2/AnimalSpec.scala: -------------------------------------------------------------------------------- 1 | package com.buransky.plugins.scoverage.samples.sbt.multiModule.module2 2 | 3 | import org.scalatest.{Matchers, FlatSpec} 4 | 5 | class AnimalSpec extends FlatSpec with Matchers { 6 | behavior of "fancy" 7 | 8 | it should "do nothing" in { 9 | Animal.fancy(Nil) should equal(Nil) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/sbt/multi-module/project/Common.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Common { 4 | val baseName = "multi-module" 5 | val scalatest = "org.scalatest" % "scalatest_2.11" % "2.2.4" 6 | } -------------------------------------------------------------------------------- /samples/sbt/multi-module/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.6 -------------------------------------------------------------------------------- /samples/sbt/multi-module/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.sbtPluginReleases 2 | 3 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.0.4") -------------------------------------------------------------------------------- /samples/sbt/multi-module/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=com.buransky:multi-module 2 | sonar.projectName=Sonar Scoverage plugin multi-module sample project 3 | sonar.projectVersion=5.1.0 4 | 5 | sonar.language=scala 6 | 7 | sonar.modules=module1,module2 8 | 9 | module1.sonar.sources=src/main/scala 10 | module1.sonar.tests=src/test/scala 11 | module1.sonar.scoverage.reportPath=target/scala-2.11/scoverage-report/scoverage.xml 12 | 13 | module2.sonar.sources=src/main/scala 14 | module2.sonar.tests=src/test/scala 15 | module2.sonar.scoverage.reportPath=target/scala-2.11/scoverage-report/scoverage.xml 16 | --------------------------------------------------------------------------------