/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 | 
82 |
83 | Multi-module project overview:
84 | 
85 |
86 | Columns with statement coverage, total number of statements and number of covered statements:
87 | 
88 |
89 | Source code markup with covered and uncovered lines:
90 | 
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 |
--------------------------------------------------------------------------------