├── CNAME
├── .java-version
├── project
├── build.properties
├── plugins.sbt
└── Platform.scala
├── src
├── test
│ ├── resources
│ │ ├── invalid-report.xml
│ │ ├── test-paths-with-different-paths.xml
│ │ ├── non-git-report.xml
│ │ └── dotcover-example.xml
│ └── scala
│ │ └── com
│ │ └── codacy
│ │ ├── model
│ │ └── configuration
│ │ │ └── CommitUUIDSpec.scala
│ │ ├── transformation
│ │ ├── PathPrefixerSpec.scala
│ │ ├── FileNameMatcherSpec.scala
│ │ └── GitFileNameUpdaterAndFilterSpec.scala
│ │ └── rules
│ │ └── commituuid
│ │ ├── CommitUUIDProviderSpec.scala
│ │ └── providers
│ │ └── GitHubActionProviderSpec.scala
├── main
│ ├── scala
│ │ └── com
│ │ │ └── codacy
│ │ │ ├── transformation
│ │ │ ├── Transformation.scala
│ │ │ ├── PathPrefixer.scala
│ │ │ ├── GitFileNameUpdaterAndFilter.scala
│ │ │ └── FileNameMatcher.scala
│ │ │ ├── rules
│ │ │ ├── file
│ │ │ │ └── GitFileFetcher.scala
│ │ │ └── commituuid
│ │ │ │ ├── providers
│ │ │ │ ├── DockerProvider.scala
│ │ │ │ ├── JenkinsProvider.scala
│ │ │ │ ├── ArgoCDProvider.scala
│ │ │ │ ├── ShippableCIProvider.scala
│ │ │ │ ├── SolanoCIProvider.scala
│ │ │ │ ├── GitlabProvider.scala
│ │ │ │ ├── GreenhouseCIProvider.scala
│ │ │ │ ├── AzurePipelinesProvider.scala
│ │ │ │ ├── AWSCodeBuildProvider.scala
│ │ │ │ ├── MagnumCIProvider.scala
│ │ │ │ ├── CircleCIProvider.scala
│ │ │ │ ├── CodefreshCIProvider.scala
│ │ │ │ ├── WerckerCIProvider.scala
│ │ │ │ ├── AppveyorProvider.scala
│ │ │ │ ├── CodeshipCIProvider.scala
│ │ │ │ ├── BitriseCIProvider.scala
│ │ │ │ ├── BuildkiteCIProvider.scala
│ │ │ │ ├── HerokuCIProvider.scala
│ │ │ │ ├── SemaphoreCIProvider.scala
│ │ │ │ ├── DroneCIProvider.scala
│ │ │ │ ├── TravisCIProvider.scala
│ │ │ │ ├── BitbucketCloudProvider.scala
│ │ │ │ ├── TeamCityProvider.scala
│ │ │ │ └── GitHubActionProvider.scala
│ │ │ │ └── CommitUUIDProvider.scala
│ │ │ ├── di
│ │ │ └── Components.scala
│ │ │ ├── CodacyCoverageReporter.scala
│ │ │ └── model
│ │ │ └── configuration
│ │ │ └── Configuration.scala
│ └── resources
│ │ └── logback.xml
└── it
│ └── scala
│ └── com
│ └── codacy
│ └── CodacyCoverageReporterSpec.scala
├── .github
├── CODEOWNERS
├── codacy-coverage-reporter.yml
├── ISSUE_TEMPLATE.md
└── workflows
│ ├── create_issue_on_label.yml
│ ├── create_issue.yml
│ └── comment_issue.yml
├── coverage-parser
└── src
│ ├── test
│ ├── resources
│ │ ├── TestSourceFile.scala
│ │ ├── TestSourceFile2.scala
│ │ ├── invalid_report.lcov
│ │ ├── test_go.out
│ │ ├── test_go_gh.out
│ │ ├── test_lcov.lcov
│ │ ├── invalid_report.xml
│ │ ├── phpunitxml
│ │ │ ├── Api
│ │ │ │ └── Api.php.xml
│ │ │ ├── CodacyPhpCoverage.php.xml
│ │ │ ├── Git
│ │ │ │ └── GitClient.php.xml
│ │ │ ├── Report
│ │ │ │ ├── CoverageReport.php.xml
│ │ │ │ ├── JsonProducer.php.xml
│ │ │ │ └── FileReport.php.xml
│ │ │ ├── Config.php.xml
│ │ │ └── Parser
│ │ │ │ ├── Parser.php.xml
│ │ │ │ └── PhpUnitXmlParser.php.xml
│ │ ├── go
│ │ │ ├── original_package.out
│ │ │ └── changed_package_name.out
│ │ ├── test_clover_with_paths.xml
│ │ ├── thousand_sep_cobertura.xml
│ │ ├── windows_paths_cobertura.xml
│ │ ├── test_cobertura.xml
│ │ ├── test_opencover.xml
│ │ └── test_dotcover.xml
│ └── scala
│ │ └── com
│ │ └── codacy
│ │ └── parsers
│ │ ├── JacocoParserTest.scala
│ │ ├── CoverageParserTest.scala
│ │ ├── LCOVParserTest.scala
│ │ ├── OpenCoverParserTest.scala
│ │ ├── CoverageParserFactoryTest.scala
│ │ ├── GoParserTest.scala
│ │ ├── DotCoverParserTest.scala
│ │ ├── CoberturaParserTest.scala
│ │ ├── PhpUnitXmlParserTest.scala
│ │ └── CloverParserTest.scala
│ └── main
│ └── scala
│ └── com
│ └── codacy
│ └── parsers
│ ├── util
│ ├── MathUtils.scala
│ ├── TextUtils.scala
│ └── XMLoader.scala
│ ├── XmlReportParser.scala
│ ├── CoverageParser.scala
│ └── implementation
│ ├── JacocoParser.scala
│ ├── CoberturaParser.scala
│ ├── DotcoverParser.scala
│ ├── OpenCoverParser.scala
│ ├── LCOVParser.scala
│ ├── PhpUnitXmlParser.scala
│ ├── GoParser.scala
│ └── CloverParser.scala
├── orbs
├── @orb.yml
├── examples
│ └── codacy-coverage-report.yml
└── commands
│ └── send_report.yml
├── api-scala
└── src
│ ├── main
│ └── scala
│ │ └── com
│ │ └── codacy
│ │ └── api
│ │ ├── client
│ │ ├── Request.scala
│ │ ├── RequestTimeout.scala
│ │ ├── RequestSuccess.scala
│ │ ├── RequestResponse.scala
│ │ └── CodacyClient.scala
│ │ ├── util
│ │ └── JsonOps.scala
│ │ ├── helpers
│ │ ├── FileHelper.scala
│ │ └── vcs
│ │ │ └── GitClient.scala
│ │ ├── CoverageReport.scala
│ │ └── service
│ │ └── CoverageServices.scala
│ └── test
│ └── scala
│ └── com
│ └── codacy
│ └── api
│ └── GitClientTest.scala
├── integration-tests
├── mock-server-error-config.json
└── mock-server-config.json
├── Dockerfile
├── SECURITY.md
├── LICENSE
├── .gitignore
├── .scalafmt.conf
├── graalvm
└── build-deps.sh
└── README.md
/CNAME:
--------------------------------------------------------------------------------
1 | coverage.codacy.com
--------------------------------------------------------------------------------
/.java-version:
--------------------------------------------------------------------------------
1 | oracle64-1.8.0.25
2 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.9.7
2 |
--------------------------------------------------------------------------------
/src/test/resources/invalid-report.xml:
--------------------------------------------------------------------------------
1 | invalid report
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @codacy/toss
2 | README.md @codacy/techwriters
3 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/TestSourceFile.scala:
--------------------------------------------------------------------------------
1 | class TestSourceFile()
2 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/TestSourceFile2.scala:
--------------------------------------------------------------------------------
1 | class TestSourceFile2()
2 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/invalid_report.lcov:
--------------------------------------------------------------------------------
1 | DA:1,1
2 | SF:foobar.scala
3 | DA:1,0
4 |
--------------------------------------------------------------------------------
/orbs/@orb.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | description: >
4 | Orb to send Codacy coverage reports
5 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/test_go.out:
--------------------------------------------------------------------------------
1 | mode: set
2 | example.com/m/v2/hello.go:5.13,7.2 1 0
3 | example.com/m/v2/hello.go:11.30,14.2 2 1
4 | example.com/m/v2/hello.go:17.35,19.2 1 1
5 |
--------------------------------------------------------------------------------
/api-scala/src/main/scala/com/codacy/api/client/Request.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api.client
2 |
3 | case class Request[T](endpoint: String, classType: Class[T], queryParameters: Map[String, String] = Map.empty)
4 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/test_go_gh.out:
--------------------------------------------------------------------------------
1 | mode: set
2 | github.com/orgname/reponame/src/hello.go:5.13,7.2 1 0
3 | github.com/orgname/reponame/src/hello.go:11.30,14.2 2 1
4 | github.com/orgname/reponame/src/hello.go:17.35,19.2 1 1
5 |
--------------------------------------------------------------------------------
/integration-tests/mock-server-error-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "httpRequest": {
3 | "path": "/2.0/coverage.*"
4 | },
5 | "httpResponse": {
6 | "statusCode": 400,
7 | "body": { "error": "Error posting coverage" }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/api-scala/src/main/scala/com/codacy/api/client/RequestTimeout.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api.client
2 |
3 | /**
4 | * The socket connection and read timeouts in milliseconds.
5 | */
6 | case class RequestTimeout(connTimeoutMs: Int, readTimeoutMs: Int)
7 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/test_lcov.lcov:
--------------------------------------------------------------------------------
1 | SF:coverage-parser/src/test/resources/TestSourceFile.scala
2 | DA:3,0
3 | DA:4,1
4 | DA:5,1
5 | DA:6,2
6 | SF:coverage-parser/src/test/resources/TestSourceFile2.scala
7 | DA:1,1
8 | DA:2,1
9 | DA:3,1
10 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/transformation/Transformation.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.transformation
2 |
3 | import com.codacy.api.CoverageReport
4 |
5 | trait Transformation {
6 |
7 | def execute(report: CoverageReport): CoverageReport
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/invalid_report.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 3
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.15.3
2 |
3 | ARG nativeImageLocation=target/graalvm-native-image/codacy-coverage-reporter
4 |
5 | COPY ${nativeImageLocation} /app/codacy-coverage-reporter
6 |
7 | WORKDIR /code
8 |
9 | ENTRYPOINT [ "/app/codacy-coverage-reporter" ]
10 |
--------------------------------------------------------------------------------
/integration-tests/mock-server-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "httpRequest": {
3 | "body": {"fileReports":[{"filename":".circleci/config.yml","coverage":{"10":1,"21":1,"9":1,"13":1,"17":1,"19":0,"15":0}}]}
4 | },
5 | "httpResponse": {
6 | "statusCode": 200,
7 | "body": {"success": "Report sent!"}
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/api-scala/src/main/scala/com/codacy/api/client/RequestSuccess.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api.client
2 |
3 | import play.api.libs.json.{Json, Reads}
4 |
5 | case class RequestSuccess(success: String)
6 |
7 | object RequestSuccess {
8 | implicit val requestSuccessReads: Reads[RequestSuccess] = Json.reads[RequestSuccess]
9 | }
10 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.4")
2 | addSbtPlugin("com.codacy" % "codacy-sbt-plugin" % "25.1.1")
3 |
4 | // Publish
5 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.4")
6 |
7 | // Coverage
8 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11")
9 |
10 | libraryDependencySchemes ++= Seq("org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always)
11 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Supported versions receive security patches.
6 |
7 | | Version | Supported |
8 | | ------- | ------------------ |
9 | | 13.x.y | :white_check_mark: |
10 | | <= 12 | :x: |
11 |
12 | ## Reporting a Vulnerability
13 |
14 | Please follow [Codacy's Responsible Disclosure Policy](https://security.codacy.com/#responsible-disclosure)
15 |
--------------------------------------------------------------------------------
/.github/codacy-coverage-reporter.yml:
--------------------------------------------------------------------------------
1 | name: codacy-coverage-reporter
2 |
3 | on: ["push"]
4 |
5 | jobs:
6 | codacy-coverage-reporter:
7 | runs-on: ubuntu-latest
8 | name: codacy-coverage-reporter
9 | steps:
10 | - uses: actions/checkout@master
11 | - name: Run codacy-coverage-reporter
12 | uses: codacy/codacy-coverage-reporter-action@master
13 | with:
14 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
15 |
--------------------------------------------------------------------------------
/api-scala/src/main/scala/com/codacy/api/util/JsonOps.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api.util
2 |
3 | import play.api.libs.json.{JsError, JsPath, Json, JsonValidationError}
4 |
5 | import scala.collection
6 |
7 | object JsonOps {
8 |
9 | def handleConversionFailure(error: collection.Seq[(JsPath, collection.Seq[JsonValidationError])]): String = {
10 | val jsonError = Json.stringify(JsError.toJson(error.toList))
11 | s"Json conversion error: $jsonError"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/util/MathUtils.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.util
2 |
3 | object MathUtils {
4 |
5 | implicit class ParseIntOps(val s: String) extends AnyVal {
6 | def toIntOrMaxValue: Int = BigInt(s).toIntOrMaxValue
7 | }
8 |
9 | implicit class BigIntOps(val bigInt: BigInt) extends AnyVal {
10 |
11 | def toIntOrMaxValue: Int =
12 | if (bigInt.isValidInt) bigInt.toInt
13 | else Int.MaxValue
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/orbs/examples/codacy-coverage-report.yml:
--------------------------------------------------------------------------------
1 | description: Send coverage report to Codacy
2 | usage:
3 | jobs:
4 | codacy-coverage-report:
5 | docker:
6 | - image: circleci/openjdk:8-jdk
7 | steps:
8 | - checkout
9 | - "run commands to generate the coverage result"
10 | - coverage-reporter/send_report:
11 | coverage-reports: "report.xml,coverage.info"
12 | project-token: $CODACY_PROJECT_TOKEN
13 | version: 2.1
14 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 |
6 | %date %-16level %-10.-10logger %message %n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/transformation/PathPrefixer.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.transformation
2 |
3 | import com.codacy.api.CoverageReport
4 |
5 | class PathPrefixer(prefix: String) extends Transformation {
6 |
7 | override def execute(report: CoverageReport): CoverageReport = {
8 | val fileReports = report.fileReports.map { fileReport =>
9 | fileReport.copy(filename = prefix + fileReport.filename)
10 | }
11 |
12 | report.copy(fileReports = fileReports)
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/api-scala/src/main/scala/com/codacy/api/helpers/FileHelper.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api.helpers
2 |
3 | import java.io.{File, PrintWriter}
4 |
5 | import play.api.libs.json._
6 |
7 | import scala.util.Try
8 |
9 | object FileHelper {
10 |
11 | def writeJsonToFile[A](file: File, value: A)(implicit writes: Writes[A]): Boolean = {
12 | val reportJson = Json.stringify(Json.toJson(value))
13 | val printWriter = new PrintWriter(file)
14 | val result = Try(printWriter.write(reportJson)).isSuccess
15 | printWriter.close()
16 | result
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015 Codacy.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /logs/
2 | project/project/
3 | project/target/
4 | target/
5 | tmp
6 | .history
7 | /dist/
8 | /.idea
9 | /*.iml
10 | .cache/
11 | /out/
12 | /.idea_modules/
13 | .classpath
14 | .project
15 | /RUNNING_PID
16 | /.settings
17 | .DS_Store
18 | *.iml
19 | codacy.pylint.conf
20 | npm-debug.log
21 | config
22 | .bloop
23 | .metals/
24 | .vscode/
25 | .codacy.json
26 | codacy-coverage-reporter-linux-*
27 | codacy-coverage-reporter-darwin-*
28 | classes/
29 | src/test/resources/codacy-coverage.json
30 | /.codacy-coverage/
31 | musl.tar.gz
32 | src/graal/bundle
33 | project/metals.sbt
34 | site
35 | .bsp
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/file/GitFileFetcher.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.file
2 |
3 | import com.codacy.api.helpers.vcs.GitClient
4 |
5 | import java.io.File
6 | import scala.util.{Failure, Success}
7 |
8 | class GitFileFetcher {
9 |
10 | def forCommit(commitSha: String): Either[String, Seq[String]] = {
11 |
12 | new GitClient(new File(System.getProperty("user.dir"))).getRepositoryFileNames(commitSha) match {
13 | case Failure(e) =>
14 | Left(s"Could not retrieve files from local Git directory, error message: ${e.getMessage}")
15 | case Success(files) => Right(files)
16 | }
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/api-scala/src/test/scala/com/codacy/api/GitClientTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api
2 |
3 | import com.codacy.api.helpers.vcs.GitClient
4 | import java.nio.file.Paths
5 | import org.scalatest.matchers.should.Matchers
6 | import org.scalatest.flatspec.AnyFlatSpec
7 | import org.scalatest.OptionValues._
8 |
9 | class GitClientTest extends AnyFlatSpec with Matchers {
10 |
11 | "GitClient" should "latestCommitUuid" in {
12 |
13 | val file = Paths.get("").toAbsolutePath.toFile
14 |
15 | val latest: Option[String] = new GitClient(file).latestCommitUuid()
16 |
17 | latest shouldNot be(None)
18 |
19 | latest.value shouldNot be(Symbol("empty"))
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/DockerProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Docker provider */
7 | object DockerProvider extends CommitUUIDProvider {
8 | val name: String = "Docker"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.contains("DOCKER_REPO")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("SOURCE_COMMIT"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/JenkinsProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Jenkins CI provider */
7 | object JenkinsProvider extends CommitUUIDProvider {
8 | val name: String = "Jenkins CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.contains("JENKINS_URL")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("GIT_COMMIT"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/ArgoCDProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /* Argo CD Provider */
7 | object ArgoCDProvider extends CommitUUIDProvider {
8 | val name: String = "Argo CD"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.contains("ARGOCD_APP_SOURCE_REPO_URL")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("ARGOCD_APP_REVISION"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/ShippableCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Shippable CI provider */
7 | object ShippableCIProvider extends CommitUUIDProvider {
8 | val name: String = "Shippable CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("SHIPPABLE").contains("true")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("COMMIT"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/SolanoCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Solano CI provider */
7 | object SolanoCIProvider extends CommitUUIDProvider {
8 | val name: String = "Solano CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("TDDIUM").contains("true")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("TDDIUM_CURRENT_COMMIT"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/GitlabProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Gitlab CI provider */
7 | object GitlabProvider extends CommitUUIDProvider {
8 | val name: String = "Gitlab CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.contains("GITLAB_CI")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("CI_COMMIT_SHA") orElse environment.get("CI_BUILD_REF"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/GreenhouseCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Greenhouse CI provider */
7 | object GreenhouseCIProvider extends CommitUUIDProvider {
8 | val name: String = "Greenhouse CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("GREENHOUSE").contains("true")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("GREENHOUSE_COMMIT"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/AzurePipelinesProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Azure Pipelines provider */
7 | object AzurePipelinesProvider extends CommitUUIDProvider {
8 | val name: String = "Azure Pipelines"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("TF_BUILD").contains("True")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("BUILD_SOURCEVERSION"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/AWSCodeBuildProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** AWS CodeBuild provider */
7 | object AWSCodeBuildProvider extends CommitUUIDProvider {
8 | val name: String = "AWS CodeBuild"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CODEBUILD_BUILD_ID").isDefined
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("CODEBUILD_RESOLVED_SOURCE_VERSION"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/MagnumCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Magnum CI provider */
7 | object MagnumCIProvider extends CommitUUIDProvider {
8 | val name: String = "Magnum CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("true") && environment.get("MAGNUM").contains("true")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("CI_COMMIT"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/CircleCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Circle CI provider */
7 | object CircleCIProvider extends CommitUUIDProvider {
8 | val name: String = "Circle CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("true") && environment.get("CIRCLECI").contains("true")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("CIRCLE_SHA1"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/CodefreshCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Codefresh CI provider */
7 | object CodefreshCIProvider extends CommitUUIDProvider {
8 | val name: String = "Codefresh CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.contains("CF_BUILD_URL") && environment.contains("CF_BUILD_ID")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("CF_REVISION"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/WerckerCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Wercker CI provider */
7 | object WerckerCIProvider extends CommitUUIDProvider {
8 | val name: String = "Wercker CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("true") && environment.contains("WERCKER_GIT_BRANCH")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("WERCKER_GIT_COMMIT"))
16 | }
17 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/util/TextUtils.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.util
2 |
3 | import java.text.NumberFormat
4 | import java.util.Locale
5 |
6 | import scala.util.Try
7 |
8 | object TextUtils {
9 |
10 | def asFloat(str: String): Float = {
11 | Try(str.toFloat).getOrElse {
12 | // The french locale uses the comma as a sep.
13 | val instance = NumberFormat.getInstance(Locale.FRANCE)
14 | val number = instance.parse(str)
15 | number.floatValue()
16 | }
17 | }
18 |
19 | def sanitiseFilename(filename: String): String = {
20 | filename
21 | .replaceAll("""\\/""", "/") // Fix for paths with \/
22 | .replace("\\", "/") // Fix for paths with \
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/AppveyorProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Appveyor provider */
7 | object AppveyorProvider extends CommitUUIDProvider {
8 | val name: String = "Appveyor CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("True") && environment.get("APPVEYOR").contains("True")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("APPVEYOR_REPO_COMMIT"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/CodeshipCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Codeship CI provider */
7 | object CodeshipCIProvider extends CommitUUIDProvider {
8 | val name: String = "Codeship CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("true") && environment.get("CI_NAME").contains("codeship")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("CI_COMMIT_ID"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/BitriseCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Bitrise CI provider */
7 | object BitriseCIProvider extends CommitUUIDProvider {
8 | val name: String = "Bitrise CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("true") && environment.get("BITRISE_IO").contains("true")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("GIT_CLONE_COMMIT_HASH"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/BuildkiteCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Buildkite CI provider */
7 | object BuildkiteCIProvider extends CommitUUIDProvider {
8 | val name: String = "Buildkite CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("true") && environment.get("BUILDKITE").contains("true")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("BUILDKITE_COMMIT"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/HerokuCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Heroku CI provider */
7 | object HerokuCIProvider extends CommitUUIDProvider {
8 | val name: String = "Heroku CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("true") && environment.contains("HEROKU_TEST_RUN_ID")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("HEROKU_TEST_RUN_COMMIT_VERSION"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/SemaphoreCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Semaphore CI provider */
7 | object SemaphoreCIProvider extends CommitUUIDProvider {
8 | val name: String = "Semaphore CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("true") && environment.get("SEMAPHORE").contains("true")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("SEMAPHORE_GIT_SHA"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/DroneCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /* Drone CI Provider */
7 | object DroneCIProvider extends CommitUUIDProvider {
8 | val name: String = "Drone CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("true") && environment.get("DRONE").contains("true")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("DRONE_COMMIT") orElse environment.get("DRONE_COMMIT_SHA"))
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/TravisCIProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Travis CI provider */
7 | object TravisCIProvider extends CommitUUIDProvider {
8 | val name: String = "Travis CI"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | environment.get("CI").contains("true") && environment.get("TRAVIS").contains("true")
12 | }
13 |
14 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
15 | parseEnvironmentVariable(environment.get("TRAVIS_PULL_REQUEST_SHA") orElse environment.get("TRAVIS_COMMIT"))
16 | }
17 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/util/XMLoader.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.util
2 |
3 | import scala.xml.factory.XMLLoader
4 | import javax.xml.parsers.SAXParserFactory
5 | import scala.xml.{Elem, SAXParser}
6 |
7 | object XMLoader extends XMLLoader[Elem] {
8 |
9 | override def parser: SAXParser = {
10 | val f = SAXParserFactory.newInstance()
11 | f.setNamespaceAware(false)
12 | f.setValidating(false)
13 | f.setFeature("http://xml.org/sax/features/namespaces", false)
14 | f.setFeature("http://xml.org/sax/features/validation", false)
15 | f.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false)
16 | f.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
17 | f.newSAXParser()
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/test/scala/com/codacy/model/configuration/CommitUUIDSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.model.configuration
2 |
3 | import org.scalatest.EitherValues
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | class CommitUUIDSpec extends AnyWordSpec with Matchers with EitherValues {
8 | "isValid" should {
9 | "approve commit uuids with 40 hexadecimal chars" in {
10 | CommitUUID.fromString("ce280928adbdc852b8eefb0c57cf400f5f95db01") shouldBe Symbol("right")
11 | CommitUUID.fromString("ce280928adbdc852b8eefb0c57cf400f5f95db0") shouldBe Symbol("left")
12 | CommitUUID.fromString("ce280928adbdc852b8eefb0c57cf400f5f95db012") shouldBe Symbol("left")
13 | CommitUUID.fromString("40 random characters that are not hexa!!") shouldBe Symbol("left")
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/api-scala/src/main/scala/com/codacy/api/CoverageReport.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api
2 |
3 | import play.api.libs.json.{JsNumber, JsObject, Json, Writes}
4 |
5 | case class CoverageFileReport(filename: String, coverage: Map[Int, Int])
6 |
7 | case class CoverageReport(fileReports: Seq[CoverageFileReport])
8 |
9 | object CoverageReport {
10 | implicit val mapWrites: Writes[Map[Int, Int]] = Writes[Map[Int, Int]] { (map: Map[Int, Int]) =>
11 | JsObject(map.map {
12 | case (key, value) => (key.toString, JsNumber(value))
13 | })
14 | }
15 | implicit val coverageFileReportWrites: Writes[CoverageFileReport] = Json.writes[CoverageFileReport]
16 | implicit val coverageReportWrites: Writes[CoverageReport] = Json.writes[CoverageReport]
17 | }
18 |
19 | object OrganizationProvider extends Enumeration {
20 | val manual, gh, bb, ghe, bbe, gl, gle = Value
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/BitbucketCloudProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** Bitbucket Cloud Pipeline provider */
7 | object BitbucketCloudProvider extends CommitUUIDProvider {
8 | val name: String = "Bitbucket Cloud Pipeline"
9 |
10 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
11 | // CI is a bit generic and could be used by other CI Providers as well
12 | // Check on Bitbucket Build Number as well
13 | environment.contains("CI") && environment.contains("BITBUCKET_BUILD_NUMBER")
14 | }
15 |
16 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] =
17 | parseEnvironmentVariable(environment.get("BITBUCKET_COMMIT"))
18 | }
19 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/phpunitxml/Api/Api.php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/phpunitxml/CodacyPhpCoverage.php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/api-scala/src/main/scala/com/codacy/api/client/RequestResponse.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api.client
2 |
3 | sealed trait RequestResponse[+A]
4 |
5 | case class SuccessfulResponse[A](value: A) extends RequestResponse[A]
6 |
7 | case class FailedResponse(message: String) extends RequestResponse[Nothing]
8 |
9 | object RequestResponse {
10 |
11 | def success[A](a: A): RequestResponse[A] = SuccessfulResponse(a)
12 |
13 | def failure[A](message: String): RequestResponse[A] = FailedResponse(message: String)
14 |
15 | def apply[A](r1: RequestResponse[Seq[A]], r2: RequestResponse[Seq[A]]): RequestResponse[Seq[A]] = {
16 | r1 match {
17 | case SuccessfulResponse(v1) =>
18 | r2 match {
19 | case SuccessfulResponse(v2) =>
20 | SuccessfulResponse(v1 ++ v2)
21 | case f @ FailedResponse(_) => f
22 | }
23 | case f @ FailedResponse(_) => f
24 | }
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/.scalafmt.conf:
--------------------------------------------------------------------------------
1 | version = "1.5.1"
2 | style = IntelliJ
3 |
4 | align = none
5 | assumeStandardLibraryStripMargin = false
6 | binPack.literalArgumentLists = true
7 | binPack.parentConstructors = false
8 | continuationIndent.defnSite = 4
9 | danglingParentheses = true
10 | docstrings = ScalaDoc
11 | includeCurlyBraceInSelectChains = true
12 | lineEndings = unix
13 | maxColumn = 120
14 | newlines.alwaysBeforeTopLevelStatements = true
15 | newlines.penalizeSingleSelectMultiArgList = false
16 | newlines.sometimesBeforeColonInMethodReturnType = true
17 | optIn.breakChainOnFirstMethodDot = true
18 | project.git = true
19 | rewrite.rules = [ SortImports, PreferCurlyFors ]
20 | spaces.afterKeywordBeforeParen = true
21 |
22 | project.includeFilters = [".*\\.sbt$", ".*\\.scala$"]
23 | project.excludeFilters = [".*\\.scala.html$", "target/.*", "modules/admin/target/.*"]
24 | onTestFailure = "To fix this, run `scalafmt` within sbt or `sbt scalafmt` on the project base directory"
25 |
--------------------------------------------------------------------------------
/graalvm/build-deps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e +o pipefail
4 |
5 | if [ "$ARCH" == "arm" ];
6 | then
7 | mkdir -p $HOME/.gcc
8 | cd $HOME/.gcc
9 | curl https://ftp.gnu.org/gnu/gcc/gcc-11.2.0/gcc-11.2.0.tar.xz --output gcc.tgz
10 | tar -xf gcc.tgz
11 | TOOLCHAIN_DIR=$HOME/.gcc/gcc-11.2.0
12 | else
13 | mkdir -p $HOME/.musl
14 | cd $HOME/.musl
15 | echo "http://more.musl.cc/10/x86_64-linux-musl/x86_64-linux-musl-native.tgz --output musl.tgz"
16 | curl http://more.musl.cc/10/x86_64-linux-musl/x86_64-linux-musl-native.tgz --output musl.tgz
17 | tar -xf musl.tgz
18 | TOOLCHAIN_DIR=$HOME/.musl/x86_64-linux-musl-native
19 | export CC=$TOOLCHAIN_DIR/bin/gcc
20 | fi
21 |
22 |
23 | zlib='zlib-1.2.13'
24 | zlibtargz=$zlib.tar.gz
25 | curl https://zlib.net/fossils/$zlibtargz --output $zlibtargz
26 | tar -xf $zlibtargz
27 |
28 | (
29 | cd $zlib
30 | ./configure --prefix=$TOOLCHAIN_DIR --static
31 | make -j "$(nproc)"
32 | sudo make install
33 | )
34 |
35 | echo "$TOOLCHAIN_DIR/bin"
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/TeamCityProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import com.codacy.model.configuration.CommitUUID
4 | import com.codacy.rules.commituuid.CommitUUIDProvider
5 |
6 | /** TeamCity CI provider */
7 | object TeamCityProvider extends CommitUUIDProvider {
8 | val name: String = "TeamCity CI"
9 |
10 | override val commitNotFoundMessage: String = s"""Can't find $name commit UUID in the environment.
11 | |TEAMCITY_BUILD_COMMIT or BUILD_VCS_NUMBER is not set. Add this to your config:
12 | | env.TEAMCITY_BUILD_COMMIT = %system.build.vcs.number%
13 | |""".stripMargin
14 |
15 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
16 | environment.contains("TEAMCITY_VERSION")
17 | }
18 |
19 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] = {
20 | parseEnvironmentVariable(environment.get("TEAMCITY_BUILD_COMMIT") orElse environment.get("BUILD_VCS_NUMBER"))
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/di/Components.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.di
2 |
3 | import com.codacy.api.client.CodacyClient
4 | import com.codacy.api.service.CoverageServices
5 | import com.codacy.model.configuration.{ApiTokenAuthenticationConfig, Configuration, ProjectTokenAuthenticationConfig}
6 | import com.codacy.rules.ReportRules
7 | import com.codacy.rules.file.GitFileFetcher
8 |
9 | class Components(private val validatedConfig: Configuration) {
10 | lazy val reportRules = new ReportRules(coverageServices, new GitFileFetcher)
11 |
12 | lazy private val (projectToken, apiToken) = validatedConfig.baseConfig.authentication match {
13 | case ProjectTokenAuthenticationConfig(projectToken) =>
14 | (Some(projectToken), None)
15 | case ApiTokenAuthenticationConfig(apiToken, _, _, _) =>
16 | (None, Some(apiToken))
17 | }
18 |
19 | lazy val codacyClient = new CodacyClient(
20 | Some(validatedConfig.baseConfig.codacyApiBaseUrl),
21 | apiToken,
22 | projectToken,
23 | validatedConfig.baseConfig.skipSslVerification
24 | )
25 |
26 | lazy val coverageServices = new CoverageServices(codacyClient)
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/scala/com/codacy/transformation/PathPrefixerSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.transformation
2 |
3 | import com.codacy.api.{CoverageFileReport, CoverageReport}
4 | import org.scalatest._
5 | import org.scalatest.wordspec.AnyWordSpec
6 | import org.scalatest.matchers.should.Matchers
7 |
8 | class PathPrefixerSpec extends AnyWordSpec with Matchers {
9 |
10 | val report = CoverageReport(
11 | Seq(CoverageFileReport("Filename.scala", Map.empty), CoverageFileReport("OtherFile.scala", Map.empty))
12 | )
13 |
14 | "PathPrefixer" should {
15 | "prefix all filenames in a report" in {
16 | val prefixer = new PathPrefixer("folder/")
17 |
18 | prefixer.execute(report).fileReports.foreach { fileReport =>
19 | fileReport.filename should startWith("folder/")
20 | }
21 | }
22 | }
23 |
24 | it should {
25 | "do nothing if no prefix is specified" in {
26 | val prefixer = new PathPrefixer("")
27 |
28 | prefixer.execute(report).fileReports.map(_.filename) should contain("Filename.scala")
29 | prefixer.execute(report).fileReports.map(_.filename) should contain("OtherFile.scala")
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/resources/test-paths-with-different-paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/transformation/GitFileNameUpdaterAndFilter.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.transformation
2 | import com.codacy.api.CoverageReport
3 | import com.codacy.transformation.FileNameMatcher.getFilenameFromPath
4 | import wvlet.log.LogSupport
5 |
6 | class GitFileNameUpdaterAndFilter(acceptableFileNamesMap: Map[String, Seq[String]])
7 | extends Transformation
8 | with LogSupport {
9 | override def execute(report: CoverageReport): CoverageReport = {
10 | val fileReports = for {
11 | fileReport <- report.fileReports
12 | fileName <- matchAndReturnName(fileReport.filename)
13 |
14 | newFileReport = fileReport.copy(filename = fileName)
15 | } yield newFileReport
16 | report.copy(fileReports = fileReports)
17 | }
18 |
19 | private def matchAndReturnName(filename: String): Option[String] = {
20 | val maybeFilename = FileNameMatcher
21 | .matchAndReturnName(filename, acceptableFileNamesMap.getOrElse(getFilenameFromPath(filename), Seq.empty))
22 |
23 | if (maybeFilename.isEmpty)
24 | logger
25 | .warn(s"File: Ignoring $filename for coverage calculation. No matching file found in the repository.")
26 |
27 | maybeFilename
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/scala/com/codacy/transformation/FileNameMatcherSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.transformation
2 |
3 | import org.scalatest.wordspec.AnyWordSpec
4 | import org.scalatest.matchers.should.Matchers
5 | import org.scalatest.OptionValues
6 |
7 | class FileNameMatcherSpec extends AnyWordSpec with Matchers with OptionValues {
8 |
9 | "matchAndReturnName" should {
10 | "return name from closest match" in {
11 | // ARRANGE
12 | val filenames = Seq("src/folder/package/file.txt", "src/folder/package/another-package/file.txt")
13 | val filename = "package/file.txt"
14 |
15 | // ACT
16 | val newFilename = FileNameMatcher.matchAndReturnName(filename, filenames)
17 |
18 | // ASSERT
19 | newFilename.isDefined shouldBe true
20 | newFilename.value shouldBe "src/folder/package/file.txt"
21 | }
22 |
23 | "return empty when name doesn't match any filenames" in {
24 | // ARRANGE
25 | val filenames = Seq("src/folder/package/file.txt", "src/folder/package/another-package/file.txt")
26 | val filename = "another-package/non-existent-file.txt"
27 |
28 | // ACT
29 | val newFilename = FileNameMatcher.matchAndReturnName(filename, filenames)
30 |
31 | // ASSERT
32 | newFilename.isDefined shouldBe false
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/XmlReportParser.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api.CoverageReport
6 | import com.codacy.parsers.util.XMLoader
7 |
8 | import scala.util.{Failure, Success, Try}
9 | import scala.xml.{Elem, NodeSeq}
10 |
11 | trait XmlReportParser {
12 | def validateSchema(xml: Elem): Boolean
13 |
14 | def getRootNode(xml: Elem): NodeSeq
15 |
16 | def loadXmlReport(reportFile: File, schemaErrorMessage: String): Either[String, NodeSeq] = {
17 | Try(XMLoader.loadFile(reportFile)) match {
18 | case Success(xml) if validateSchema(xml) =>
19 | Right(getRootNode(xml))
20 |
21 | case Success(_) =>
22 | Left(s"Invalid report. $schemaErrorMessage.")
23 |
24 | case Failure(ex) =>
25 | Left(s"Unparseable report. ${ex.getMessage}")
26 | }
27 | }
28 |
29 | private def getFailedParseMessage(ex: Throwable) =
30 | s"Failed to parse report with error: ${ex.getMessage}"
31 |
32 | def parseReport(reportFile: File, schemaErrorMessage: String)(
33 | parseReport: NodeSeq => Either[String, CoverageReport]
34 | ): Either[String, CoverageReport] = {
35 | loadXmlReport(reportFile, schemaErrorMessage)
36 | .flatMap { report =>
37 | Try(parseReport(report)) match {
38 | case Success(coverageReport) => coverageReport
39 | case Failure(ex) => Left(getFailedParseMessage(ex))
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/go/original_package.out:
--------------------------------------------------------------------------------
1 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:31.101,35.16 3 1
2 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:39.2,39.41 1 1
3 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:47.2,47.41 1 1
4 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:56.2,58.71 2 1
5 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:62.2,62.18 1 1
6 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:35.16,38.3 2 1
7 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:39.41,46.3 2 1
8 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:47.41,54.3 2 1
9 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:58.71,60.3 1 1
10 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:70.104,72.9 2 1
11 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:76.2,76.60 1 1
12 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:81.2,82.16 2 1
13 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:92.2,92.15 1 1
14 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:96.2,96.12 1 1
15 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:72.9,74.3 1 0
16 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:76.60,78.3 1 1
17 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:82.16,90.3 2 1
18 | github.com/codacy/pulse/modules/entity/src/internal/api/api.go:92.15,94.3 1 1
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/test_clover_with_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/go/changed_package_name.out:
--------------------------------------------------------------------------------
1 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:31.101,35.16 3 1
2 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:39.2,39.41 1 1
3 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:47.2,47.41 1 1
4 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:56.2,58.71 2 1
5 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:62.2,62.18 1 1
6 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:35.16,38.3 2 1
7 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:39.41,46.3 2 1
8 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:47.41,54.3 2 1
9 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:58.71,60.3 1 1
10 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:70.104,72.9 2 1
11 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:76.2,76.60 1 1
12 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:81.2,82.16 2 1
13 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:92.2,92.15 1 1
14 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:96.2,96.12 1 1
15 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:72.9,74.3 1 0
16 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:76.60,78.3 1 1
17 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:82.16,90.3 2 1
18 | github.com/codacy/pulse/modules/entity/src/internal/apiv1/api.go:92.15,94.3 1 1
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/thousand_sep_cobertura.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/windows_paths_cobertura.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/transformation/FileNameMatcher.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.transformation
2 |
3 | import java.nio.file.Paths
4 | import scala.util.Try
5 |
6 | object FileNameMatcher {
7 |
8 | def matchAndReturnName(filename: String, fileNames: Seq[String]): Option[String] = {
9 | fileNames
10 | .filter(name => isTheSameFile(filename.toLowerCase, name.toLowerCase))
11 | .sortBy(name => Math.abs(filename.length - name.length))
12 | .headOption
13 | }
14 |
15 | def getFilenameFromPath(filename: String): String = {
16 | Try(Paths.get(filename).getFileName.toString.toLowerCase).getOrElse(filename.toLowerCase)
17 | }
18 |
19 | private def normalizePath(path: String): String = {
20 | path.replace("\\", "/")
21 | }
22 |
23 | private def haveSameName(file: String, covFile: String): Boolean =
24 | getFilenameFromPath(file) == getFilenameFromPath(covFile)
25 |
26 | private def haveSamePath(file: String, covFile: String): Boolean =
27 | normalizePath(file) == normalizePath(covFile)
28 |
29 | private def fileEndsWithReportPath(file: String, covFile: String): Boolean =
30 | normalizePath(file).endsWith(normalizePath(covFile))
31 |
32 | private def reportEndsWithFilePath(file: String, covFile: String): Boolean =
33 | normalizePath(covFile).endsWith(normalizePath(file))
34 |
35 | private def isTheSameFile(file: String, covFile: String): Boolean = {
36 | haveSameName(file, covFile) && (haveSamePath(file, covFile) ||
37 | fileEndsWithReportPath(file, covFile) ||
38 | reportEndsWithFilePath(file, covFile))
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/scala/com/codacy/parsers/JacocoParserTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api._
6 | import com.codacy.parsers.implementation.JacocoParser
7 | import org.scalatest.{BeforeAndAfterAll, EitherValues}
8 | import org.scalatest.wordspec.AnyWordSpec
9 | import org.scalatest.matchers.should.Matchers
10 |
11 | class JacocoParserTest extends AnyWordSpec with BeforeAndAfterAll with Matchers with EitherValues {
12 |
13 | "JacocoParser" should {
14 |
15 | "identify if report is invalid" in {
16 | val reader =
17 | JacocoParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_cobertura.xml"))
18 | reader.isLeft shouldBe true
19 | }
20 |
21 | "identify if report is valid" in {
22 | val reader =
23 | JacocoParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_jacoco.xml"))
24 | reader.isRight shouldBe true
25 | }
26 |
27 | "return a valid report" in {
28 |
29 | val reader = JacocoParser
30 | .parse(new File("."), new File("coverage-parser/src/test/resources/test_jacoco.xml"))
31 |
32 | val testReport = CoverageReport(
33 | List(
34 | CoverageFileReport(
35 | "org/eluder/coverage/sample/InnerClassCoverage.java",
36 | Map(10 -> 1, 6 -> 1, 9 -> 1, 13 -> 1, 22 -> 1, 27 -> 0, 12 -> 1, 3 -> 1, 16 -> 1, 26 -> 0, 19 -> 1)
37 | ),
38 | CoverageFileReport("org/eluder/coverage/sample/SimpleCoverage.java", Map(3 -> 1, 6 -> 1, 10 -> 0, 11 -> 0))
39 | )
40 | )
41 |
42 | reader.value should equal(testReport)
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/scala/com/codacy/parsers/CoverageParserTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 | import com.codacy.parsers.implementation._
5 |
6 | import org.scalatest.{BeforeAndAfterAll, EitherValues}
7 | import org.scalatest.wordspec.AnyWordSpec
8 | import org.scalatest.matchers.should.Matchers
9 |
10 | class CoverageParserTest extends AnyWordSpec with BeforeAndAfterAll with Matchers with EitherValues {
11 | private val coberturaReportPath = "coverage-parser/src/test/resources/test_cobertura.xml"
12 | private val cloverReportPath = "coverage-parser/src/test/resources/test_clover.xml"
13 |
14 | "parse" should {
15 | "return the specific error" when {
16 | "the file cannot be parsed with a specific parser" in {
17 | val reader = CoverageParser.parse(new File("."), new File(coberturaReportPath), Some(CloverParser))
18 |
19 | reader shouldBe Symbol("left")
20 | }
21 | "the file cannot be parsed with another specific parser" in {
22 | val reader = CoverageParser.parse(new File("."), new File(coberturaReportPath), Some(LCOVParser))
23 |
24 | reader shouldBe Symbol("left")
25 | }
26 | }
27 | "return a valid result" when {
28 | "file and format are matching cobertura" in {
29 | val reader =
30 | CoverageParser.parse(new File("."), new File(coberturaReportPath), Some(CoberturaParser))
31 |
32 | reader shouldBe Symbol("right")
33 | }
34 | "file and format are matching clover" in {
35 | val reader = CoverageParser.parse(new File("."), new File(cloverReportPath), Some(CloverParser))
36 |
37 | reader shouldBe Symbol("right")
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/phpunitxml/Git/GitClient.php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/test_cobertura.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/CodacyCoverageReporter.scala:
--------------------------------------------------------------------------------
1 | package com.codacy
2 |
3 | import com.codacy.configuration.parser.{CommandConfiguration, ConfigurationParsingApp}
4 | import com.codacy.di.Components
5 | import com.codacy.model.configuration.{Configuration, FinalConfig, ReportConfig}
6 | import com.codacy.rules.ConfigurationRules
7 | import wvlet.airframe.log
8 | import wvlet.log.{LogSupport, Logger}
9 |
10 | object CodacyCoverageReporter extends ConfigurationParsingApp with LogSupport {
11 | log.initNoColor
12 |
13 | def run(commandConfig: CommandConfiguration): Int = {
14 | val configRules = new ConfigurationRules(commandConfig, sys.env)
15 |
16 | val noAvailableTokens =
17 | configRules.getProjectToken(commandConfig.baseConfig).isEmpty &&
18 | configRules.getApiToken(commandConfig.baseConfig).isEmpty
19 | if (commandConfig.baseConfig.skipValue && noAvailableTokens) {
20 | logger.info("Skip reporting coverage")
21 | 0
22 | } else {
23 | sendReport(configRules.validatedConfig) match {
24 | case Right(message) =>
25 | logger.info(message)
26 | 0
27 | case Left(message) =>
28 | logger.error(message)
29 | 1
30 | }
31 | }
32 | }
33 |
34 | private def sendReport(validatedConfig: Either[String, Configuration]) = {
35 | validatedConfig.flatMap { validatedConfig =>
36 | val components = new Components(validatedConfig)
37 |
38 | if (validatedConfig.baseConfig.debug) {
39 | Logger("com.codacy").setLogLevel(wvlet.log.LogLevel.DEBUG)
40 | }
41 |
42 | validatedConfig match {
43 | case config: ReportConfig =>
44 | components.reportRules.codacyCoverage(config)
45 |
46 | case config: FinalConfig =>
47 | components.reportRules.finalReport(config)
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/test/scala/com/codacy/rules/commituuid/CommitUUIDProviderSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid
2 |
3 | import org.scalatest._
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | class CommitUUIDProviderSpec extends AnyWordSpec with Matchers with EitherValues {
8 | "getFromEnvironment" should {
9 | "provide a valid commit uuid" in {
10 | val envVars =
11 | Map("JENKINS_URL" -> "https://jenkins.example.com/", "GIT_COMMIT" -> "ad7ce1b9973d31a2794565f892b6ae4cab575d7c")
12 | val commitUuid = CommitUUIDProvider.getFromEnvironment(envVars)
13 |
14 | commitUuid should be(Symbol("right"))
15 | commitUuid.value.value should be("ad7ce1b9973d31a2794565f892b6ae4cab575d7c")
16 | }
17 |
18 | "provide the first valid commit uuid, in provider order" in {
19 | val envVars =
20 | Map(
21 | "JENKINS_URL" -> "https://jenkins.example.com/",
22 | "GIT_COMMIT" -> "ad7ce1b9973d31a2794565f892b6ae4cab575d7c",
23 | "GITLAB_CI" -> "true",
24 | "CI_COMMIT_SHA" -> "1b097ecbbdd0204f908087d6fe1b94dc3453eaf9"
25 | )
26 | val commitUuid = CommitUUIDProvider.getFromEnvironment(envVars)
27 |
28 | commitUuid should be(Symbol("right"))
29 | commitUuid.value.value should be("1b097ecbbdd0204f908087d6fe1b94dc3453eaf9")
30 | }
31 |
32 | "not provide a commit uuid if the environment is empty" in {
33 | val commitUuid = CommitUUIDProvider.getFromEnvironment(Map.empty)
34 | commitUuid should be(Symbol("left"))
35 | }
36 |
37 | "not provide a commit uuid if the environment has no valid commits" in {
38 | val envVars =
39 | Map("JENKINS_URL" -> "https://jenkins.example.com/", "GIT_COMMIT" -> "Commit UUID")
40 | val commitUuid = CommitUUIDProvider.getFromEnvironment(envVars)
41 |
42 | commitUuid should be(Symbol("left"))
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/project/Platform.scala:
--------------------------------------------------------------------------------
1 | object Platform {
2 | sealed abstract class OS(val string: String) extends Product with Serializable
3 |
4 | object OS {
5 | case object Windows extends OS("windows")
6 | case object MacOS extends OS("osx")
7 | case object Linux extends OS("linux")
8 | case object Unknown extends OS("unknown")
9 |
10 | val all: List[OS] = List(Windows, MacOS, Linux, Unknown)
11 |
12 | def detect(osNameProp: String): OS = normalise(osNameProp) match {
13 | case p if p.startsWith("linux") => OS.Linux
14 | case p if p.startsWith("windows") => OS.Windows
15 | case p if p.startsWith("osx") || p.startsWith("macosx") => OS.MacOS
16 | case _ => OS.Unknown
17 | }
18 | }
19 |
20 | sealed abstract class Arch extends Product with Serializable {}
21 |
22 | object Arch {
23 | case object Intel extends Arch {}
24 | case object Arm extends Arch {}
25 |
26 | val all: List[Arch] = List(Intel, Arm)
27 |
28 | def detect(osArchProp: String): Arch = normalise(osArchProp) match {
29 | case "amd64" | "x64" | "x8664" | "x86" => Intel
30 | case "aarch64" | "arm64" => Arm
31 | }
32 | }
33 |
34 | sealed abstract class Bits extends Product with Serializable
35 |
36 | object Bits {
37 | case object x32 extends Bits
38 | case object x64 extends Bits
39 |
40 | def detect(sunArchProp: String): Bits =
41 | sunArchProp match {
42 | case "64" => x64
43 | case "32" => x32
44 | }
45 | }
46 |
47 | case class Target(os: OS, arch: Arch, bits: Bits)
48 |
49 | lazy val os: OS = OS.detect(sys.props.getOrElse("os.name", ""))
50 | lazy val arch: Arch = Arch.detect(sys.props.getOrElse("os.arch", ""))
51 | lazy val bits: Bits = Bits.detect(sys.props.getOrElse("sun.arch.data.model", ""))
52 | lazy val target: Target = Target(os, arch, bits)
53 |
54 | private def normalise(s: String) =
55 | s.toLowerCase(java.util.Locale.US).replaceAll("[^a-z0-9]+", "")
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Are you looking for help?
2 |
3 | This is an issue tracker, used to manage and track the development of this [Codacy](https://www.codacy.com/) project.
4 |
5 | It is not a platform support system. If think your problem is related with our platform at https://www.codacy.com/, please contact us through our [contact form](https://www.codacy.com/contact) or our internal chat application, visible after you login on the bottom right corner.
6 |
7 | Keep in mind that this issue tracker is for specific problems of this project.
8 |
9 | ### Scala Version (2.10.x / etc)
10 |
11 |
12 | ### Operating System (Ubuntu 15.10 / MacOS 10.10 / Windows 10)
13 |
14 | Use `uname -a` if on Linux.
15 |
16 | ### JDK (Oracle 1.8.0_72, OpenJDK 1.8.x, Azul Zing)
17 |
18 | Paste the output from `java -version` at the command line.
19 |
20 | ### Library Dependencies
21 |
22 | If this is an issue that involves integration with another system, include the exact version and OS of the other system, including any intermediate drivers or APIs i.e. if you connect to a PostgreSQL database, include both the version / OS of PostgreSQL and the JDBC driver version used to connect to the database.
23 |
24 | ### Expected Behavior
25 |
26 | Please describe the expected behavior of the issue, starting from the first action.
27 |
28 | 1.
29 | 2.
30 | 3.
31 |
32 | ### Actual Behavior
33 |
34 | Please provide a description of what actually happens, working from the same starting point.
35 |
36 | Be descriptive: "it doesn't work" does not describe what the behavior actually is -- instead, say "when sending the coverage with the command (...) it returns the output error (...)"
37 |
38 | 1.
39 | 2.
40 | 3.
41 |
42 | ### Reproducible Test Case
43 |
44 | Please provide a some information on how to reproduce the bug. A PR with a failing test would be awesome, if possible.
45 |
46 | If the issue is more complex or requires configuration, please provide a link to a project on Github/Codacy that reproduces the issue.
47 |
--------------------------------------------------------------------------------
/orbs/commands/send_report.yml:
--------------------------------------------------------------------------------
1 | description: "Download Codacy's coverage reporter and run it"
2 |
3 | parameters:
4 | tool_version:
5 | type: string
6 | default: ''
7 | description: "Specify Codacy's coverage reporter tool version"
8 | project-token:
9 | type: string
10 | default: ${CODACY_PROJECT_TOKEN}
11 | description: Specify Codacy's project token
12 | coverage-reports:
13 | type: string
14 | description: 'Optional comma separated list of coverage reports to send to Codacy'
15 | default: ''
16 | skip:
17 | type: boolean
18 | description: Skip if token isn't defined. Useful to let forks CI pass without passing secrets
19 | default: false
20 |
21 | steps:
22 | - run:
23 | name: Upload Coverage Results to Codacy
24 | command: |
25 | export CODACY_REPORTER_VERSION=<< parameters.tool_version >>
26 |
27 | export CODACY_PROJECT_TOKEN=<< parameters.project-token >>
28 | # comma separated list of report files
29 | # reference from https://unix.stackexchange.com/a/191125
30 | # plus https://stackoverflow.com/a/20323801/7898052
31 | report_array=$(printf "<< parameters.coverage-reports >>" | cut -d',' -f1)
32 |
33 | params=''
34 | for report in $report_array
35 | do
36 | if [ ! -z "$report" ]
37 | then
38 | params="$params -r $report"
39 | fi
40 | done
41 |
42 | if << parameters.skip >>; then
43 | skip_option="--skip"
44 | else
45 | skip_option=""
46 | fi
47 |
48 |
49 | if [ -x "$(which curl)" ]; then
50 | curl -Ls https://coverage.codacy.com/get.sh > get.sh
51 | elif [ -x "$(which wget)" ] ; then
52 | wget -qO - https://coverage.codacy.com/get.sh > get.sh
53 | else
54 | printf "Could not find curl or wget, please install one."
55 | fi
56 |
57 | source get.sh report $params --partial $skip_option &&\
58 | source get.sh final $skip_option
59 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/scala/com/codacy/parsers/LCOVParserTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api._
6 | import com.codacy.parsers.implementation.LCOVParser
7 | import org.scalatest.{BeforeAndAfterAll, EitherValues}
8 | import org.scalatest.wordspec.AnyWordSpec
9 | import org.scalatest.matchers.should.Matchers
10 |
11 | class LCOVParserTest extends AnyWordSpec with BeforeAndAfterAll with Matchers with EitherValues {
12 |
13 | "LCOVParser" should {
14 |
15 | "identify if report is invalid" in {
16 | val reader = LCOVParser.parse(new File("."), new File("coverage-parser/src/test/resources/invalid_report.lcov"))
17 | reader.isLeft shouldBe true
18 | }
19 |
20 | "identify if report is invalid beacuse is cobertura format" in {
21 | val reader = LCOVParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_cobertura.xml"))
22 | reader.isLeft shouldBe true
23 | }
24 |
25 | "identify if report is invalid beacuse is clover format" in {
26 | val reader = LCOVParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_clover.xml"))
27 | reader.isLeft shouldBe true
28 | }
29 |
30 | "identify if report is valid" in {
31 | val reader = LCOVParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_lcov.lcov"))
32 | reader.isRight shouldBe true
33 | }
34 |
35 | "return a valid report" in {
36 | val reader = LCOVParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_lcov.lcov"))
37 |
38 | val testReport = CoverageReport(
39 | List(
40 | CoverageFileReport("coverage-parser/src/test/resources/TestSourceFile2.scala", Map(1 -> 1, 2 -> 1, 3 -> 1)),
41 | CoverageFileReport(
42 | "coverage-parser/src/test/resources/TestSourceFile.scala",
43 | Map(3 -> 0, 4 -> 1, 5 -> 1, 6 -> 2)
44 | )
45 | )
46 | )
47 |
48 | reader.value should equal(testReport)
49 | }
50 |
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/scala/com/codacy/parsers/OpenCoverParserTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api.{CoverageFileReport, CoverageReport}
6 | import com.codacy.parsers.implementation.OpenCoverParser
7 | import org.scalatest.{BeforeAndAfterAll, EitherValues}
8 | import org.scalatest.wordspec.AnyWordSpec
9 | import org.scalatest.matchers.should.Matchers
10 |
11 | class OpenCoverParserTest extends AnyWordSpec with BeforeAndAfterAll with Matchers with EitherValues {
12 | private val openCoverReportPath = "coverage-parser/src/test/resources/test_opencover.xml"
13 | private val nonExistentReportPath = "coverage-parser/src/test/resources/non_existent.xml"
14 | private val coberturaReportPath = "coverage-parser/src/test/resources/test_cobertura.xml"
15 | "parse" should {
16 | "return an invalid report" when {
17 | "report file does not exist" in {
18 | val reader = OpenCoverParser.parse(new File("."), new File(nonExistentReportPath))
19 | reader shouldBe Symbol("left")
20 | }
21 |
22 | "report file has a different format" in {
23 | val reader = OpenCoverParser.parse(new File("."), new File(coberturaReportPath))
24 | reader shouldBe Symbol("left")
25 | }
26 | }
27 |
28 | "return a valid report" in {
29 | val reader = OpenCoverParser.parse(new File("."), new File(openCoverReportPath))
30 | reader shouldBe Symbol("right")
31 | }
32 |
33 | "return the expected files" in {
34 | val reader = OpenCoverParser.parse(new File("."), new File(openCoverReportPath))
35 |
36 | reader.value.fileReports.map(_.filename).sorted shouldBe Seq("bar.cs", "foo.cs", "foobar.cs").sorted
37 | }
38 |
39 | "return the expected report" in {
40 | val reader = OpenCoverParser.parse(new File("."), new File(openCoverReportPath))
41 |
42 | reader.value shouldBe CoverageReport(
43 | List(
44 | CoverageFileReport("foo.cs", Map(10 -> 1)),
45 | CoverageFileReport("bar.cs", Map(10 -> 0)),
46 | CoverageFileReport("foobar.cs", Map(10 -> 0, 20 -> 1))
47 | )
48 | )
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/api-scala/src/main/scala/com/codacy/api/helpers/vcs/GitClient.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api.helpers.vcs
2 |
3 | import java.io.File
4 | import java.util.Date
5 | import org.eclipse.jgit.api.Git
6 | import org.eclipse.jgit.lib.{Repository, RepositoryBuilder}
7 | import org.eclipse.jgit.revwalk.RevWalk
8 | import org.eclipse.jgit.treewalk.TreeWalk
9 |
10 | import scala.collection.mutable.ListBuffer
11 | import scala.jdk.CollectionConverters.*
12 | import scala.util.Try
13 |
14 | case class CommitInfo(uuid: String, authorName: String, authorEmail: String, date: Date)
15 |
16 | class GitClient(workDirectory: File) {
17 |
18 | val repositoryTry: Try[Repository] = Try(new RepositoryBuilder().findGitDir(workDirectory).readEnvironment().build())
19 |
20 | val repository: Option[Repository] = repositoryTry.toOption
21 |
22 | def latestCommitUuid(): Option[String] = {
23 | repositoryTry
24 | .map { rep =>
25 | val git = new Git(rep)
26 | val headRev = git.log().setMaxCount(1).call().asScala.head
27 | headRev.getName
28 | }
29 | .toOption
30 | .filter(_.trim.nonEmpty)
31 | }
32 |
33 | def latestCommitInfo: Try[CommitInfo] = {
34 | repositoryTry.map { rep =>
35 | val git = new Git(rep)
36 | val headRev = git.log().setMaxCount(1).call().asScala.head
37 | val authorIdent = headRev.getAuthorIdent
38 |
39 | CommitInfo(headRev.getName, authorIdent.getName, authorIdent.getEmailAddress, authorIdent.getWhen)
40 | }
41 | }
42 |
43 | def getRepositoryFileNames(commitSha: String): Try[Seq[String]] = {
44 | repositoryTry.map { rep =>
45 | val git = new Git(rep)
46 | val repo = git.getRepository
47 | val commitId = repo.resolve(commitSha)
48 | val revWalk = new RevWalk(repo)
49 | val commit = revWalk.parseCommit(commitId)
50 | val tree = commit.getTree
51 | val treeWalk = new TreeWalk(repo)
52 | treeWalk.addTree(tree)
53 | treeWalk.setRecursive(true)
54 |
55 | val buffer = new ListBuffer[String]
56 |
57 | while (treeWalk.next) {
58 | buffer.addOne(treeWalk.getPathString)
59 | }
60 | buffer.toList
61 | }
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/CoverageParser.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api.CoverageReport
6 | import com.codacy.parsers.implementation._
7 |
8 | import scala.util.Try
9 |
10 | trait CoverageParser {
11 | val name: String
12 |
13 | def parse(rootProject: File, reportFile: File): Either[String, CoverageReport]
14 | }
15 |
16 | object CoverageParser {
17 |
18 | final case class CoverageParserResult(report: CoverageReport, parser: CoverageParser)
19 |
20 | val allParsers: List[CoverageParser] =
21 | List(
22 | CoberturaParser,
23 | JacocoParser,
24 | CloverParser,
25 | OpenCoverParser,
26 | DotcoverParser,
27 | PhpUnitXmlParser,
28 | LCOVParser,
29 | GoParser
30 | )
31 |
32 | def parse(projectRoot: File, reportFile: File): Either[String, CoverageReport] = {
33 | parse(projectRoot = projectRoot, reportFile = reportFile, None).map(_.report)
34 | }
35 |
36 | def parse(
37 | projectRoot: File,
38 | reportFile: File,
39 | forceParser: Option[CoverageParser]
40 | ): Either[String, CoverageParserResult] = {
41 | val isEmptyReport = {
42 | // Just starting by detecting the simplest case: a single report file
43 | Try(reportFile.isFile && reportFile.length() == 0).getOrElse(false)
44 | }
45 |
46 | val parsers = forceParser match {
47 | case Some(parser) => List(parser)
48 | case _ => allParsers
49 | }
50 |
51 | object ParsedCoverage {
52 | def unapply(parser: CoverageParser): Option[CoverageParserResult] = {
53 | parser.parse(projectRoot, reportFile).toOption.map(CoverageParserResult(_, parser))
54 | }
55 | }
56 |
57 | if (isEmptyReport) {
58 | Left(s"Report file ${reportFile.getCanonicalPath} is empty")
59 | } else {
60 | parsers.view
61 | .collectFirst {
62 | case ParsedCoverage(value: CoverageParserResult) => Right(value)
63 | }
64 | .getOrElse(
65 | Left(s"Could not parse report, unrecognized report format (tried: ${parsers.map(_.name).mkString(", ")})")
66 | )
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/scala/com/codacy/parsers/CoverageParserFactoryTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api.{CoverageFileReport, CoverageReport}
6 | import org.scalatest.{BeforeAndAfterAll}
7 | import org.scalatest.wordspec.AnyWordSpec
8 | import org.scalatest.matchers.should.Matchers
9 |
10 | class CoverageParserFactoryTest extends AnyWordSpec with BeforeAndAfterAll with Matchers {
11 |
12 | "CoverageParserFactory" should {
13 |
14 | "get report with unspecified parser" in {
15 | val expectedReport = CoverageReport(
16 | List(
17 | CoverageFileReport(
18 | "coverage-parser/src/test/resources/TestSourceFile.scala",
19 | Map(5 -> 1, 10 -> 1, 6 -> 2, 9 -> 1, 3 -> 0, 4 -> 1, 7 -> 1, 8 -> 3, 9 -> Int.MaxValue)
20 | ),
21 | CoverageFileReport("coverage-parser/src/test/resources/TestSourceFile2.scala", Map(1 -> 1, 2 -> 1, 3 -> 1))
22 | )
23 | )
24 |
25 | CoverageParser
26 | .parse(new File("."), new File("coverage-parser/src/test/resources/test_cobertura.xml")) shouldEqual Right(
27 | expectedReport
28 | )
29 | }
30 |
31 | "get report with jacoco parser" in {
32 | val expectedReport = CoverageReport(
33 | List(
34 | CoverageFileReport(
35 | "org/eluder/coverage/sample/InnerClassCoverage.java",
36 | Map(10 -> 1, 6 -> 1, 9 -> 1, 13 -> 1, 22 -> 1, 27 -> 0, 12 -> 1, 3 -> 1, 16 -> 1, 26 -> 0, 19 -> 1)
37 | ),
38 | CoverageFileReport("org/eluder/coverage/sample/SimpleCoverage.java", Map(3 -> 1, 6 -> 1, 10 -> 0, 11 -> 0))
39 | )
40 | )
41 |
42 | CoverageParser
43 | .parse(new File("."), new File("coverage-parser/src/test/resources/test_jacoco.xml")) shouldEqual Right(
44 | expectedReport
45 | )
46 | }
47 |
48 | "fail to get invalid report" in {
49 | CoverageParser.parse(new File("."), new File("invalid_report.xml")) shouldEqual Left(
50 | "Could not parse report, unrecognized report format (tried: Cobertura, Jacoco, Clover, OpenCover, DotCover, PHPUnit, LCOV, Go)"
51 | )
52 | }
53 |
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/implementation/JacocoParser.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.implementation
2 |
3 | import java.io.File
4 | import com.codacy.api._
5 | import com.codacy.parsers.util.TextUtils
6 | import com.codacy.parsers.{CoverageParser, XmlReportParser}
7 |
8 | import scala.xml.{Elem, Node, NodeSeq}
9 |
10 | private case class LineCoverage(missedInstructions: Int, coveredInstructions: Int)
11 |
12 | object JacocoParser extends CoverageParser with XmlReportParser {
13 |
14 | override val name: String = "Jacoco"
15 |
16 | private val ReportTag = "report"
17 |
18 | override def parse(projectRoot: File, reportFile: File): Either[String, CoverageReport] =
19 | parseReport(reportFile, s"Could not find top level <$ReportTag> tag") {
20 | parseReportNode(projectRoot, _)
21 | }
22 |
23 | override def validateSchema(xml: Elem): Boolean = getRootNode(xml).nonEmpty
24 |
25 | override def getRootNode(xml: Elem): NodeSeq = xml \\ ReportTag
26 |
27 | private def parseReportNode(projectRoot: File, report: NodeSeq): Either[String, CoverageReport] = {
28 | val projectRootStr: String = TextUtils.sanitiseFilename(projectRoot.getAbsolutePath)
29 | val filesCoverage = for {
30 | pkg <- report \\ "package"
31 | packageName = (pkg \@ "name")
32 | sourceFile <- pkg \\ "sourcefile"
33 | } yield {
34 | val filename =
35 | TextUtils
36 | .sanitiseFilename(s"$packageName/${(sourceFile \@ "name")}")
37 | .stripPrefix(projectRootStr)
38 | .stripPrefix("/")
39 | lineCoverage(filename, sourceFile)
40 | }
41 | Right(CoverageReport(filesCoverage))
42 | }
43 |
44 | private def lineCoverage(filename: String, fileNode: Node): CoverageFileReport = {
45 | val lineHitMap: Map[Int, Int] = (fileNode \\ "line").view
46 | .map { line =>
47 | (line \@ "nr").toInt -> LineCoverage((line \@ "mi").toInt, (line \@ "ci").toInt)
48 | }
49 | .collect {
50 | case (key, lineCoverage) if lineCoverage.missedInstructions + lineCoverage.coveredInstructions > 0 =>
51 | key -> (if (lineCoverage.coveredInstructions > 0) 1 else 0)
52 | }
53 | .toMap
54 |
55 | CoverageFileReport(filename, lineHitMap)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/implementation/CoberturaParser.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.implementation
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api.{CoverageFileReport, CoverageReport}
6 | import com.codacy.parsers.util.MathUtils._
7 | import com.codacy.parsers.util.TextUtils
8 | import com.codacy.parsers.{CoverageParser, XmlReportParser}
9 |
10 | import scala.collection.mutable
11 | import scala.xml.{Elem, NodeSeq}
12 |
13 | object CoberturaParser extends CoverageParser with XmlReportParser {
14 |
15 | override val name: String = "Cobertura"
16 |
17 | private val CoverageTag = "coverage"
18 | private val LineRateAttribute = "line-rate"
19 |
20 | override def parse(projectRoot: File, reportFile: File): Either[String, CoverageReport] = {
21 | parseReport(reportFile, s"Could not find top level <$CoverageTag> tag") { node =>
22 | Right(parseReportNode(projectRoot, node))
23 | }
24 | }
25 |
26 | // restricting the schema to
27 | // ensures this will not consider Clover reports which also have a tag
28 | override def validateSchema(xml: Elem): Boolean = (xml \\ CoverageTag \ s"@$LineRateAttribute").nonEmpty
29 |
30 | override def getRootNode(xml: Elem): NodeSeq = xml \\ CoverageTag
31 |
32 | private def parseReportNode(projectRoot: File, report: NodeSeq) = {
33 | val projectRootStr: String = TextUtils.sanitiseFilename(projectRoot.getAbsolutePath)
34 |
35 | val fileReports: List[CoverageFileReport] = (for {
36 | (filename, classes) <- (report \\ "class").groupBy(c => c \@ "filename")
37 | } yield {
38 | val cleanFilename = TextUtils.sanitiseFilename(filename).stripPrefix(projectRootStr).stripPrefix("/")
39 | lineCoverage(cleanFilename, classes)
40 | }).toList
41 |
42 | CoverageReport(fileReports)
43 | }
44 |
45 | private def lineCoverage(sourceFilename: String, classes: NodeSeq): CoverageFileReport = {
46 | val map = mutable.Map.empty[Int, Int]
47 |
48 | for {
49 | xClass <- classes
50 | line <- xClass \\ "line"
51 | } {
52 | val key = (line \@ "number").toInt
53 | val value = (line \@ "hits").toIntOrMaxValue
54 | val sum = map.get(key).getOrElse(0) + BigInt(value)
55 |
56 | map(key) = sum.toIntOrMaxValue
57 | }
58 |
59 | CoverageFileReport(sourceFilename, map.toMap)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/scala/com/codacy/parsers/GoParserTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api._
6 |
7 | import com.codacy.parsers.implementation.GoParser
8 | import org.scalatest.{EitherValues}
9 | import org.scalatest.wordspec.AnyWordSpec
10 | import org.scalatest.matchers.should.Matchers
11 |
12 | class GoParserTest extends AnyWordSpec with Matchers with EitherValues {
13 |
14 | "parse" should {
15 |
16 | "fail to parse an invalid report" when {
17 |
18 | "the report file does not exist" in {
19 | // Arrange
20 | val nonExistentReportPath = "coverage-parser/src/test/resources/non-existent.xml"
21 |
22 | // Act
23 | val parseResult = GoParser.parse(new File("."), new File(nonExistentReportPath))
24 |
25 | // Assert
26 | parseResult shouldBe Left("Can't load report file.")
27 | }
28 | }
29 |
30 | "return a valid report from github" in {
31 | val reader = GoParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_go_gh.out"))
32 |
33 | val testReport = CoverageReport(
34 | List(
35 | CoverageFileReport(
36 | "src/hello.go",
37 | Map(5 -> 0, 14 -> 1, 6 -> 0, 13 -> 1, 17 -> 1, 12 -> 1, 7 -> 0, 18 -> 1, 11 -> 1, 19 -> 1)
38 | )
39 | )
40 | )
41 |
42 | reader.value should equal(testReport)
43 | }
44 |
45 | "return a valid report" in {
46 | val reader = GoParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_go.out"))
47 |
48 | val testReport = CoverageReport(
49 | List(
50 | CoverageFileReport(
51 | "example.com/m/v2/hello.go",
52 | Map(5 -> 0, 14 -> 1, 6 -> 0, 13 -> 1, 17 -> 1, 12 -> 1, 7 -> 0, 18 -> 1, 11 -> 1, 19 -> 1)
53 | )
54 | )
55 | )
56 |
57 | reader.value should equal(testReport)
58 | }
59 |
60 | "return consistent values" in {
61 | //given two reports where the package names were only changed, should return the same results COV-207
62 | val reader = GoParser.parse(new File("."), new File("coverage-parser/src/test/resources/go/original_package.out"))
63 | val reader1 =
64 | GoParser.parse(new File("."), new File("coverage-parser/src/test/resources/go/changed_package_name.out"))
65 |
66 | reader.value.fileReports(0).coverage should equal(reader1.value.fileReports(0).coverage)
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/implementation/DotcoverParser.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.implementation
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api.{CoverageFileReport, CoverageReport}
6 | import com.codacy.parsers.util.TextUtils
7 | import com.codacy.parsers.{CoverageParser, XmlReportParser}
8 |
9 | import scala.xml.{Elem, NodeSeq}
10 |
11 | case class StatementNode(fileIndex: Int, line: Int, covered: Boolean)
12 |
13 | object DotcoverParser extends CoverageParser with XmlReportParser {
14 | override val name: String = "DotCover"
15 |
16 | private val RootTag = "Root"
17 | private val CoverageAttribute = "CoveragePercent"
18 | private val CoveredAttribute = "Covered"
19 |
20 | override def parse(rootProject: File, reportFile: File): Either[String, CoverageReport] =
21 | parseReport(reportFile, s"Could not find tag <$RootTag $CoverageAttribute=...>") { node =>
22 | Right(parseReportNode(rootProject, node))
23 | }
24 |
25 | override def validateSchema(xml: Elem): Boolean = (xml \\ RootTag \ s"@$CoverageAttribute").nonEmpty
26 |
27 | override def getRootNode(xml: Elem): NodeSeq = xml \\ RootTag
28 |
29 | private def parseReportNode(rootProject: File, rootNode: NodeSeq): CoverageReport = {
30 | val projectRootStr: String = TextUtils.sanitiseFilename(rootProject.getAbsolutePath)
31 |
32 | val fileIndices: Map[Int, String] = (rootNode \ "FileIndices" \ "File").map { x =>
33 | (x \@ "Index").toInt -> (x \@ "Name")
34 | }.toMap
35 |
36 | val statementsPerFile: Map[Int, NodeSeq] = (rootNode \\ "Statement").groupBy(x => (x \@ "FileIndex").toInt)
37 |
38 | val fileReports = for {
39 | (fileIndex, statements) <- statementsPerFile
40 | filename = TextUtils.sanitiseFilename(fileIndices(fileIndex)).stripPrefix(projectRootStr).stripPrefix("/")
41 | lineCoverage = getLineCoverage(statements)
42 | totalLines = lineCoverage.keys.size
43 | coveredLines = lineCoverage.values.count(_ > 0)
44 | } yield CoverageFileReport(filename, lineCoverage)
45 |
46 | CoverageReport(fileReports.toSeq)
47 | }
48 |
49 | private def getLineCoverage(statementNodes: NodeSeq) = {
50 | val lines = for {
51 | node <- statementNodes
52 | // a statement can extend over several lines
53 | line <- (node \@ "Line").toInt to (node \@ "EndLine").toInt
54 | coveredValue = if ((node \@ CoveredAttribute).toBoolean) 1 else 0
55 | } yield (line, coveredValue)
56 |
57 | lines.toMap
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/scala/com/codacy/transformation/GitFileNameUpdaterAndFilterSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.transformation
2 |
3 | import com.codacy.api.{CoverageFileReport, CoverageReport}
4 | import com.codacy.transformation.FileNameMatcher.getFilenameFromPath
5 | import org.scalatest.wordspec.AnyWordSpec
6 | import org.scalatest.matchers.should.Matchers
7 |
8 | class GitFileNameUpdaterAndFilterSpec extends AnyWordSpec with Matchers {
9 |
10 | private val acceptableFilenames =
11 | Seq("src/folder/file1.txt", "src/another-folder/file1.txt", "src/folder/file2.txt", "src/folder/file3.txt")
12 | private val acceptableFilenamesMap = acceptableFilenames.groupBy(getFilenameFromPath).view.toMap
13 | private val updaterAndFilter = new GitFileNameUpdaterAndFilter(acceptableFilenamesMap)
14 |
15 | "execute" should {
16 | "update and match filename" in {
17 | // ARRANGE
18 | val expectedFilename = "src/folder/file1.txt"
19 |
20 | val coverageReport = CoverageReport(Seq(CoverageFileReport("folder/file1.txt", Map.empty)))
21 |
22 | // ACT
23 | val result = updaterAndFilter.execute(coverageReport)
24 |
25 | // ASSERT
26 | result.fileReports.map(_.filename) shouldBe Seq(expectedFilename)
27 | }
28 |
29 | "update and match several filenames" in {
30 | // ARRANGE
31 | val expectedFilenames =
32 | Set("src/folder/file1.txt", "src/another-folder/file1.txt", "src/folder/file2.txt", "src/folder/file3.txt")
33 |
34 | val coverageReport = CoverageReport(
35 | Seq(
36 | CoverageFileReport("folder/file1.txt", Map.empty),
37 | CoverageFileReport("another-folder/file1.txt", Map.empty),
38 | CoverageFileReport("file2.txt", Map.empty),
39 | CoverageFileReport("src/folder/file3.txt", Map.empty)
40 | )
41 | )
42 |
43 | // ACT
44 | val result = updaterAndFilter.execute(coverageReport)
45 |
46 | // ASSERT
47 | result.fileReports.map(_.filename).toSet shouldBe expectedFilenames
48 | }
49 |
50 | "filters out reports not in acceptable filenames" in {
51 | // ARRANGE
52 | val coverageReport = CoverageReport(
53 | Seq(
54 | CoverageFileReport("folder/file1.txt", Map.empty),
55 | CoverageFileReport("folder/not-acceptable-file.txt", Map.empty)
56 | )
57 | )
58 |
59 | // ACT
60 | val result = updaterAndFilter.execute(coverageReport)
61 |
62 | // ASSERT
63 | result.fileReports.size shouldBe 1
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/model/configuration/Configuration.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.model.configuration
2 |
3 | import java.io.File
4 | import scala.util.matching.Regex
5 | import com.codacy.api.OrganizationProvider
6 | import com.codacy.api.client.RequestTimeout
7 | import com.codacy.parsers.CoverageParser
8 | import com.codacy.plugins.api.languages.{Language, Languages}
9 |
10 | sealed trait Configuration {
11 | def baseConfig: BaseConfig
12 | }
13 |
14 | case class ReportConfig(
15 | baseConfig: BaseConfig,
16 | languageOpt: Option[String],
17 | forceLanguage: Boolean,
18 | coverageReports: List[File],
19 | partial: Boolean,
20 | prefix: String,
21 | forceCoverageParser: Option[CoverageParser]
22 | ) extends Configuration {
23 |
24 | lazy val language: Option[Language] = languageOpt.flatMap(Languages.fromName)
25 | }
26 |
27 | case class FinalConfig(baseConfig: BaseConfig) extends Configuration
28 |
29 | sealed trait AuthenticationConfig
30 |
31 | case class ProjectTokenAuthenticationConfig(projectToken: String) extends AuthenticationConfig
32 |
33 | case class ApiTokenAuthenticationConfig(
34 | apiToken: String,
35 | organizationProvider: OrganizationProvider.Value,
36 | username: String,
37 | projectName: String
38 | ) extends AuthenticationConfig
39 |
40 | case class BaseConfig(
41 | authentication: AuthenticationConfig,
42 | codacyApiBaseUrl: String,
43 | commitUUID: Option[CommitUUID],
44 | debug: Boolean,
45 | timeout: RequestTimeout,
46 | sleepTime: Int,
47 | numRetries: Int,
48 | skipSslVerification: Boolean
49 | )
50 |
51 | sealed trait CommitUUID extends Any {
52 | def value: String
53 | }
54 |
55 | object CommitUUID {
56 | /* The number of characters in a commit UUID. */
57 | val length: Int = 40
58 |
59 | /* Regex to help detect a commit UUID. */
60 | val regex: Regex = s"[0-9a-fA-F]{$length}".r
61 |
62 | private def isValid(commitUUID: String): Boolean =
63 | commitUUID.length == length && regex.findFirstIn(commitUUID).isDefined
64 |
65 | /** Either creates a [[CommitUUID]], if `commitUUID` is valid, or returns an error string, to display to the user. */
66 | def fromString(commitUUID: String): Either[String, CommitUUID] =
67 | if (isValid(commitUUID)) {
68 | Right(CommitUUIDImpl(commitUUID))
69 | } else {
70 | Left("Commit SHA-1 hash isn't valid. Make sure it consists of 40 hexadecimal characters.")
71 | }
72 |
73 | /** Commit UUID class that guarantees it contains a valid commit SHA, since it can only be instantiated via
74 | * [[fromString()]] */
75 | private[CommitUUID] case class CommitUUIDImpl(value: String) extends AnyVal with CommitUUID
76 | }
77 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/implementation/OpenCoverParser.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.implementation
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api.{CoverageFileReport, CoverageReport}
6 | import com.codacy.parsers.util.TextUtils
7 | import com.codacy.parsers.{CoverageParser, XmlReportParser}
8 |
9 | import scala.xml.{Elem, NodeSeq}
10 |
11 | object OpenCoverParser extends CoverageParser with XmlReportParser {
12 | private val RootTag = "CoverageSession"
13 | private val IdAttribute = "uid"
14 | private val FileTag = "File"
15 | private val FileRefTag = "FileRef"
16 | private val LineAttribute = "sl"
17 | private val VisitCounterAttribute = "vc"
18 | private val FilesTag = "Files"
19 | private val FullPathAttribute = "fullPath"
20 | private val MethodTag = "Method"
21 | private val SequencePointTag = "SequencePoint"
22 |
23 | override val name: String = "OpenCover"
24 |
25 | override def parse(rootProject: File, reportFile: File): Either[String, CoverageReport] =
26 | parseReport(reportFile, s"Could not find tag <$RootTag>") { node =>
27 | Right(parseReportNode(node, TextUtils.sanitiseFilename(rootProject.getAbsolutePath)))
28 | }
29 |
30 | override def validateSchema(xml: Elem): Boolean = getRootNode(xml).nonEmpty
31 |
32 | override def getRootNode(xml: Elem): NodeSeq = xml \\ RootTag
33 |
34 | private def parseReportNode(rootNode: NodeSeq, projectRoot: String): CoverageReport = {
35 | val fileIndices: Map[Int, String] = (rootNode \\ FilesTag \ FileTag).map { n =>
36 | (n \@ IdAttribute).toInt -> n \@ FullPathAttribute
37 | }.toMap
38 |
39 | val validMethods = (rootNode \\ MethodTag).filter(m => (m \ FileRefTag).nonEmpty)
40 |
41 | val fileReports = (for {
42 | (fileIndex, methods) <- validMethods.groupBy(m => (m \ FileRefTag \@ IdAttribute).toInt)
43 | filename <- fileIndices.get(fileIndex)
44 |
45 | sanitisedFileName = TextUtils.sanitiseFilename(filename).stripPrefix(projectRoot).stripPrefix("/")
46 | lineCoverage = getLineCoverage(methods, sanitisedFileName)
47 | totalLines = lineCoverage.size
48 | coveredLines = lineCoverage.count { case (_, visitCount) => visitCount > 0 }
49 | } yield CoverageFileReport(sanitisedFileName, lineCoverage)).toSeq
50 |
51 | CoverageReport(fileReports)
52 | }
53 |
54 | private def getLineCoverage(methodNodes: NodeSeq, filename: String) = {
55 | val lineCoverage = for {
56 | methodNode <- methodNodes
57 | sequencePoint <- methodNode \\ SequencePointTag
58 | } yield (sequencePoint \@ LineAttribute).toInt -> (sequencePoint \@ VisitCounterAttribute).toInt
59 |
60 | lineCoverage.toMap
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/providers/GitHubActionProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import java.io.File
4 | import java.nio.file.Files
5 |
6 | import com.codacy.model.configuration.CommitUUID
7 | import com.codacy.rules.commituuid.CommitUUIDProvider
8 | import play.api.libs.json._
9 | import wvlet.log.LazyLogger
10 |
11 | import scala.util.Try
12 |
13 | object GitHubActionProvider extends CommitUUIDProvider with LazyLogger {
14 | val name: String = "GitHub Actions"
15 |
16 | override def validateEnvironment(environment: Map[String, String]): Boolean = {
17 | environment.get("CI").contains("true") && environment.get("GITHUB_ACTIONS").contains("true")
18 | }
19 |
20 | override def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID] = {
21 | // if the event is a pull_request or a workflow_run the GITHUB_SHA will
22 | // have a different commit UUID (the id of a merge commit)
23 | // https://help.github.com/en/actions/reference/events-that-trigger-workflows
24 | // for this reason, we need to fetch it from the event details that GitHub provides
25 | // equivalent to doing ${{github.event.pull_request.head.sha}} in a GitHub action workflow
26 | environment.get("GITHUB_EVENT_NAME") match {
27 | case Some(eventName @ ("pull_request" | "workflow_run")) =>
28 | getEventCommitSha(environment, eventName)
29 | case _ =>
30 | parseEnvironmentVariable(environment.get("GITHUB_SHA"))
31 | }
32 | }
33 |
34 | private def getEventCommitSha(envVars: Map[String, String], eventName: String): Either[String, CommitUUID] = {
35 | for {
36 | eventPath <- envVars.get("GITHUB_EVENT_PATH").toRight("Could not find event description file path")
37 | eventContent <- readFile(eventPath)
38 | sha <- extractHeadSHA(eventName = eventName, eventContent = eventContent)
39 | commitUUID <- CommitUUID.fromString(sha)
40 | } yield commitUUID
41 | }
42 |
43 | private def readFile(path: String): Either[String, String] = {
44 | val contentTry = Try {
45 | val fileBytes = Files.readAllBytes(new File(path).toPath)
46 | new String(fileBytes)
47 | }
48 |
49 | contentTry.toEither.left.map { ex =>
50 | logger.error("Failed to read event file", ex)
51 | s"Failed to read event file with error: ${ex.getMessage}"
52 | }
53 | }
54 |
55 | private def extractHeadSHA(eventName: String, eventContent: String) = {
56 | Try {
57 | val eventJson = Json.parse(eventContent)
58 | eventName match {
59 | case "workflow_run" =>
60 | (eventJson \ eventName \ "head_sha").as[String]
61 | case _ =>
62 | (eventJson \ eventName \ "head" \ "sha").as[String]
63 | }
64 | }.toEither.left.map(t => s"Unable to fetch SHA from event file. Failed with error: ${t.getMessage}")
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/test_opencover.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/phpunitxml/Report/CoverageReport.php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/scala/com/codacy/parsers/DotCoverParserTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api.{CoverageFileReport, CoverageReport}
6 | import com.codacy.parsers.implementation.DotcoverParser
7 | import org.scalatest.{BeforeAndAfterAll, EitherValues}
8 | import org.scalatest.wordspec.AnyWordSpec
9 | import org.scalatest.matchers.should.Matchers
10 |
11 | class DotCoverParserTest extends AnyWordSpec with BeforeAndAfterAll with Matchers with EitherValues {
12 | private val nonExistentFile = "coverage-parser/src/test/resources/non-existent.xml"
13 | private val dotCoverReport = "coverage-parser/src/test/resources/test_dotcover.xml"
14 | private val differentFormatReport = "coverage-parser/src/test/resources/test_cobertura.xml"
15 | "parse" should {
16 | "return an invalid report" when {
17 | "report file does not exist" in {
18 | val reader = DotcoverParser.parse(new File("."), new File(nonExistentFile))
19 |
20 | reader shouldBe Symbol("left")
21 | }
22 |
23 | "report file has a different format" in {
24 | val reader = DotcoverParser.parse(new File("."), new File(differentFormatReport))
25 |
26 | reader shouldBe Symbol("left")
27 | }
28 | }
29 |
30 | "return a valid report" in {
31 | val reader = DotcoverParser.parse(new File("."), new File(dotCoverReport))
32 |
33 | reader shouldBe Symbol("right")
34 | }
35 |
36 | "return the expected files" in {
37 | val reader = DotcoverParser.parse(new File("."), new File(dotCoverReport))
38 | reader.value.fileReports.map(_.filename).sorted shouldBe Seq(
39 | "src/Coverage/FooBar.cs",
40 | "src/Tests/FooBarTests.cs",
41 | "src/Coverage/Program.cs",
42 | "src/Coverage/Bar.cs",
43 | "src/Coverage/Foo.cs"
44 | ).sorted
45 | }
46 |
47 | "return the expected coverage report" in {
48 | val reader = DotcoverParser.parse(new File("."), new File(dotCoverReport))
49 |
50 | reader.value shouldBe CoverageReport(
51 | List(
52 | CoverageFileReport(
53 | "src/Coverage/FooBar.cs",
54 | Map(10 -> 1, 21 -> 1, 9 -> 1, 13 -> 0, 17 -> 1, 19 -> 0, 15 -> 0)
55 | ),
56 | CoverageFileReport(
57 | "src/Tests/FooBarTests.cs",
58 | Map(
59 | 14 -> 1,
60 | 20 -> 1,
61 | 28 -> 1,
62 | 22 -> 1,
63 | 27 -> 1,
64 | 12 -> 1,
65 | 31 -> 1,
66 | 11 -> 1,
67 | 23 -> 1,
68 | 30 -> 1,
69 | 19 -> 1,
70 | 15 -> 1
71 | )
72 | ),
73 | CoverageFileReport("src/Coverage/Program.cs", Map(8 -> 0, 9 -> 0, 10 -> 0)),
74 | CoverageFileReport("src/Coverage/Bar.cs", Map(10 -> 0, 14 -> 1, 9 -> 1, 12 -> 0, 11 -> 0, 8 -> 1, 15 -> 1)),
75 | CoverageFileReport("src/Coverage/Foo.cs", Map(8 -> 1, 9 -> 1, 10 -> 1))
76 | )
77 | )
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/scala/com/codacy/parsers/CoberturaParserTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api.{CoverageFileReport, CoverageReport}
6 | import com.codacy.parsers.implementation.CoberturaParser
7 | import org.scalatest.{BeforeAndAfterAll, EitherValues}
8 | import org.scalatest.matchers.should.Matchers
9 | import org.scalatest.wordspec.AnyWordSpec
10 |
11 | class CoberturaParserTest extends AnyWordSpec with BeforeAndAfterAll with Matchers with EitherValues {
12 |
13 | "CoberturaParser" should {
14 |
15 | "identify if report is invalid" in {
16 | val reader = CoberturaParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_jacoco.xml"))
17 | reader.isLeft shouldBe true
18 | }
19 |
20 | "identify if report is valid" in {
21 | val reader =
22 | CoberturaParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_cobertura.xml"))
23 | reader.isRight shouldBe true
24 | }
25 |
26 | "return a valid report" in {
27 | val reader =
28 | CoberturaParser.parse(new File("."), new File("coverage-parser/src/test/resources/test_cobertura.xml"))
29 |
30 | val testReport = CoverageReport(
31 | List(
32 | CoverageFileReport(
33 | "coverage-parser/src/test/resources/TestSourceFile.scala",
34 | Map(5 -> 1, 10 -> 1, 6 -> 2, 9 -> Int.MaxValue, 3 -> 0, 4 -> 1, 7 -> 1, 8 -> 3)
35 | ),
36 | CoverageFileReport("coverage-parser/src/test/resources/TestSourceFile2.scala", Map(1 -> 1, 2 -> 1, 3 -> 1)),
37 | )
38 | )
39 |
40 | reader.value should equal(testReport)
41 | }
42 |
43 | "not crash on thousands separators" in {
44 | val reader =
45 | CoberturaParser.parse(new File("."), new File("coverage-parser/src/test/resources/thousand_sep_cobertura.xml"))
46 |
47 | val testReport = CoverageReport(
48 | List(
49 | CoverageFileReport(
50 | "coverage-parser/src/test/resources/TestSourceFile.scala",
51 | Map(5 -> 1, 10 -> 1, 6 -> 2, 9 -> 1, 9 -> 0, 8 -> 1, 4 -> 1)
52 | ),
53 | CoverageFileReport("coverage-parser/src/test/resources/TestSourceFile2.scala", Map(1 -> 1, 2 -> 1, 3 -> 1)),
54 | )
55 | )
56 |
57 | reader.value should equal(testReport)
58 | }
59 |
60 | "return a valid report with windows file path separator" in {
61 | val reader =
62 | CoberturaParser.parse(new File("."), new File("coverage-parser/src/test/resources/windows_paths_cobertura.xml"))
63 |
64 | val testReport = CoverageReport(
65 | List(
66 | CoverageFileReport(
67 | "coverage-parser/src/test/resources/TestSourceFile.scala",
68 | Map(5 -> 1, 10 -> 1, 6 -> 2, 9 -> 1, 3 -> 0, 4 -> 1)
69 | ),
70 | CoverageFileReport("coverage-parser/src/test/resources/TestSourceFile2.scala", Map(1 -> 1, 2 -> 1, 3 -> 1)),
71 | )
72 | )
73 |
74 | reader.value should equal(testReport)
75 | }
76 |
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/phpunitxml/Config.php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/phpunitxml/Parser/Parser.php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/implementation/LCOVParser.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.implementation
2 |
3 | import com.codacy.parsers.CoverageParser
4 | import com.codacy.parsers.util.MathUtils._
5 | import com.codacy.api.{CoverageFileReport, CoverageReport}
6 | import java.io.File
7 |
8 | import com.codacy.parsers.util.XMLoader
9 |
10 | import scala.io.Source
11 | import scala.util.{Failure, Success, Try}
12 |
13 | object LCOVParser extends CoverageParser {
14 | override val name: String = "LCOV"
15 |
16 | final val SF = "SF:"
17 | final val DA = "DA:"
18 |
19 | override def parse(rootProject: File, reportFile: File): Either[String, CoverageReport] = {
20 | val report = Try(Source.fromFile(reportFile)) match {
21 | // most reports are XML, and we want to ensure the LCOV parser won't mishandle it and return an empty result
22 | case Success(lines) if Try(XMLoader.loadFile(reportFile)).isSuccess =>
23 | Left(s"The file is not in the lcov format but is an xml.")
24 | case Success(lines) =>
25 | Right(lines.getLines)
26 | case Failure(ex) =>
27 | Left(s"Can't load report file. ${ex.getMessage}")
28 | }
29 |
30 | report.flatMap(parseLines(reportFile, _))
31 | }
32 |
33 | private def parseLines(reportFile: File, lines: Iterator[String]): Either[String, CoverageReport] = {
34 | val coverageFileReports =
35 | lines.foldLeft[Either[String, Seq[CoverageFileReport]]](Right(Seq.empty[CoverageFileReport]))(
36 | (accum, next) =>
37 | accum.flatMap {
38 | case reports if next startsWith SF =>
39 | Right(CoverageFileReport(next.stripPrefix(SF), Map()) +: reports)
40 | case reports if next startsWith DA =>
41 | reports.headOption match {
42 | case Some(value) =>
43 | val coverage = next.stripPrefix(DA).split(",")
44 | if (coverage.length >= 2 && coverage.forall(_ forall Character.isDigit)) {
45 | val coverageValue = coverage.map(_.toIntOrMaxValue)
46 | Right(
47 | value.copy(coverage = value.coverage + (coverageValue(0) -> coverageValue(1))) +: reports.tail
48 | )
49 | } else Left(s"Misformatting of file ${reportFile.toString}")
50 | case _ => Left(s"Fail to parse ${reportFile.toString}")
51 | }
52 | case reports =>
53 | val res = Right(reports)
54 | res
55 | }
56 | )
57 | coverageFileReports.map { fileReports =>
58 | val totalFileReport = fileReports.map { report =>
59 | CoverageFileReport(report.filename, report.coverage)
60 | }
61 |
62 | val (covered, total) = totalFileReport
63 | .map { f =>
64 | (f.coverage.count { case (_, hit) => hit > 0 }, f.coverage.size)
65 | }
66 | .foldLeft(0 -> 0) {
67 | case ((accumCovered, accumTotal), (nextCovered, nextTotal)) =>
68 | (accumCovered + nextCovered, accumTotal + nextTotal)
69 | }
70 |
71 | CoverageReport(totalFileReport)
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/phpunitxml/Report/JsonProducer.php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/test/scala/com/codacy/rules/commituuid/providers/GitHubActionProviderSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid.providers
2 |
3 | import org.scalatest.{EitherValues}
4 | import org.scalatest.wordspec.AnyWordSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | class GitHubActionProviderSpec extends AnyWordSpec with Matchers with EitherValues {
8 | val validPullRequestCommitUuid = "480b8293f4340216b8f630053a230e2867cd5c28"
9 | val invalidPullRequestCommitUuid = "65e4436280a41c721617cba15f33555d3122907e"
10 |
11 | val validWorkflowRunCommitUuid = "0f33f2554d99ce8c12ab126fc4e06bd2ad663e50"
12 |
13 | "getUUID" should {
14 | "fail" when {
15 | "no environment variables are defined" in {
16 | val provider = GitHubActionProvider
17 | val commitUuidEither = provider.getValidCommitUUID(Map.empty)
18 | commitUuidEither should be(Symbol("left"))
19 | }
20 | "not pull request and GITHUB_SHA is empty" in {
21 | val provider = GitHubActionProvider
22 | val commitUuidEither = provider.getValidCommitUUID(Map("GITHUB_EVENT_NAME" -> "push"))
23 | commitUuidEither should be(Symbol("left"))
24 | }
25 | "github action does not have correct commit sha" in {
26 | val provider = GitHubActionProvider
27 | val envVars = Map(
28 | "GITHUB_EVENT_NAME" -> "pull_request",
29 | "GITHUB_SHA" -> invalidPullRequestCommitUuid,
30 | "GITHUB_EVENT_PATH" -> "src/test/resources/invalid-github-action-pull-request-event.json"
31 | )
32 | val commitUuidEither = provider.getValidCommitUUID(envVars)
33 | commitUuidEither should be(Symbol("left"))
34 | }
35 | }
36 | "succeed" when {
37 | "event is push and GITHUB_SHA has value" in {
38 | val provider = GitHubActionProvider
39 | val envVars = Map("GITHUB_EVENT_NAME" -> "push", "GITHUB_SHA" -> validPullRequestCommitUuid)
40 | val commitUuidEither = provider.getValidCommitUUID(envVars)
41 | commitUuidEither should be(Symbol("right"))
42 | commitUuidEither.value.value should be(validPullRequestCommitUuid)
43 | }
44 | "event is pull_request and json file includes needed information" in {
45 | val provider = GitHubActionProvider
46 | val envVars = Map(
47 | "GITHUB_EVENT_NAME" -> "pull_request",
48 | "GITHUB_SHA" -> invalidPullRequestCommitUuid,
49 | "GITHUB_EVENT_PATH" -> "src/test/resources/github-action-pull-request-event.json"
50 | )
51 | val commitUuidEither = provider.getValidCommitUUID(envVars)
52 | commitUuidEither should be(Symbol("right"))
53 | commitUuidEither.value.value should be(validPullRequestCommitUuid)
54 | }
55 | "event is workflow_run and json file includes needed information" in {
56 | val provider = GitHubActionProvider
57 | val envVars = Map(
58 | "GITHUB_EVENT_NAME" -> "workflow_run",
59 | "GITHUB_SHA" -> invalidPullRequestCommitUuid,
60 | "GITHUB_EVENT_PATH" -> "src/test/resources/github-action-workflow-run-event.json"
61 | )
62 | val commitUuidEither = provider.getValidCommitUUID(envVars)
63 | commitUuidEither should be(Symbol("right"))
64 | commitUuidEither.value.value should be(validWorkflowRunCommitUuid)
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/implementation/PhpUnitXmlParser.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.implementation
2 |
3 | import java.io.File
4 |
5 | import com.codacy.api.{CoverageFileReport, CoverageReport}
6 | import com.codacy.parsers.{CoverageParser, XmlReportParser}
7 | import com.codacy.parsers.util.TextUtils
8 |
9 | import scala.xml.{Elem, NodeSeq}
10 |
11 | object PhpUnitXmlParser extends CoverageParser with XmlReportParser {
12 | override val name: String = "PHPUnit"
13 |
14 | private val PhpUnitTag = "phpunit"
15 | private val ProjectTag = "project"
16 | private val DirectoryTag = "directory"
17 | private val XmlParseErrorMessage = s"Could not find top level <$PhpUnitTag> tag";
18 |
19 | override def parse(rootProject: File, reportFile: File): Either[String, CoverageReport] =
20 | parseReport(reportFile, XmlParseErrorMessage) {
21 | parseReportNode(rootProject, _, reportFile.getParent)
22 | }
23 |
24 | override def validateSchema(xml: Elem): Boolean = getRootNode(xml).nonEmpty
25 |
26 | override def getRootNode(xml: Elem): NodeSeq = xml \\ PhpUnitTag
27 |
28 | private def parseReportNode(
29 | projectRoot: File,
30 | report: NodeSeq,
31 | reportRootPath: String
32 | ): Either[String, CoverageReport] = {
33 | val fileNodes = report \ ProjectTag \ DirectoryTag \ "file"
34 | val projectRootPath = TextUtils.sanitiseFilename(projectRoot.getAbsolutePath)
35 | val codeDirectory = report \ ProjectTag \ DirectoryTag \@ "name"
36 | val fileReports = makeFileReports(fileNodes, projectRootPath, codeDirectory, reportRootPath)
37 | fileReports.map(CoverageReport(_))
38 | }
39 |
40 | private def makeFileReports(
41 | fileNodes: NodeSeq,
42 | projectRootPath: String,
43 | codeDirectory: String,
44 | reportRootPath: String
45 | ): Either[String, Seq[CoverageFileReport]] = {
46 | val builder = Seq.newBuilder[CoverageFileReport]
47 | var error = Option.empty[String]
48 | for (f <- fileNodes if error.isEmpty) {
49 | val reportFileName = f \@ "href"
50 | val fileName = getSourceFileName(projectRootPath, codeDirectory, reportFileName)
51 | getLineCoverage(reportRootPath, reportFileName) match {
52 | case Right(lineCoverage) =>
53 | builder += CoverageFileReport(fileName, lineCoverage)
54 | case Left(message) => error = Some(message)
55 | }
56 | }
57 | error match {
58 | case Some(value) =>
59 | Left(value)
60 | case None =>
61 | Right(builder.result())
62 | }
63 | }
64 |
65 | private def getLineCoverage(reportRootPath: String, filename: String) = {
66 | val coverageDetailFile = new File(reportRootPath, filename)
67 | val phpUnitNode = loadXmlReport(coverageDetailFile, XmlParseErrorMessage)
68 |
69 | val lineCoverage: Either[String, Map[Int, Int]] = phpUnitNode.map { node =>
70 | (node \\ "coverage" \\ "line").map { line =>
71 | (line \@ "nr").toInt -> (line \ "covered").length
72 | }.toMap
73 | }
74 | lineCoverage
75 | }
76 |
77 | private def getSourceFileName(pathToRemove: String, codeRootDirectory: String, reportRelativePath: String) = {
78 | new File(codeRootDirectory, reportRelativePath).getAbsolutePath
79 | .stripPrefix(pathToRemove)
80 | .stripPrefix("/")
81 | .stripSuffix(".xml")
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/implementation/GoParser.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.implementation
2 |
3 | import com.codacy.parsers.CoverageParser
4 |
5 | import java.io.File
6 |
7 | import com.codacy.api.{CoverageFileReport, CoverageReport}
8 |
9 | import scala.io.Source
10 | import scala.util.{Failure, Success, Try}
11 |
12 | case class GoCoverageInfo(filename: String, lineFrom: Int, lineTo: Int, numberOfStatements: Int, countOfStatements: Int)
13 |
14 | object GoParser extends CoverageParser {
15 |
16 | override val name: String = "Go"
17 |
18 | final val MODE = """mode: ([set|count|atomic]*)""".r
19 |
20 | // filename.go:lineFrom.column,lineTo.column numberOfStatements countOfStatements
21 | final val regexpString = """([a-zA-Z\/\._\-\d]*):(\d+).*?,(\d+).* (\d+) (\d+)""".r
22 |
23 | override def parse(rootProject: File, reportFile: File): Either[String, CoverageReport] = {
24 | val report = Try(Source.fromFile(reportFile)) match {
25 | case Success(lines) =>
26 | Right(lines.getLines)
27 | case Failure(ex) =>
28 | Left("Can't load report file.")
29 | }
30 |
31 | report.map(lines => parseLines(lines.toList))
32 | }
33 |
34 | private def parseLines(lines: List[String]): CoverageReport = {
35 | val coverageInfo = parseAllCoverageInfo(lines)
36 | val coverageInfoGroupedByFilename: Map[String, Set[GoCoverageInfo]] =
37 | coverageInfo.toSet.groupBy((a: GoCoverageInfo) => a.filename)
38 |
39 | val coverageFileReports =
40 | coverageInfoGroupedByFilename.foldLeft[Seq[CoverageFileReport]](Seq.empty[CoverageFileReport])((accum, next) => {
41 | next match {
42 | case (filename, coverageInfosForFile) =>
43 | // Only for Github - Process filename to remove "github.com/orgname/repositoryname/"
44 | val processedFilename = if (filename.startsWith("github.com/")) {
45 | filename.split("/", 4).lastOption.getOrElse(filename) // Get everything after repository name
46 | } else {
47 | filename
48 | }
49 |
50 | // Calculate hits for a file for given statement reports
51 | val coverage = coverageInfosForFile.foldLeft(Map[Int, Int]()) {
52 | case (hitMapAcc, coverageInfo) =>
53 | // Calculate the range of lines the statement has
54 | val lines = Range.inclusive(coverageInfo.lineFrom, coverageInfo.lineTo)
55 |
56 | // For each line, add the number of hits
57 | hitMapAcc ++ lines.foldLeft(Map[Int, Int]()) {
58 | case (statementHitMapAcc, line) =>
59 | statementHitMapAcc ++
60 | // If the line is already present on the hit map, don't replace the value
61 | Map(line -> (hitMapAcc.getOrElse(line, 0) + coverageInfo.countOfStatements))
62 | }
63 | }
64 |
65 | accum :+ CoverageFileReport(processedFilename, coverage)
66 | }
67 | })
68 |
69 | CoverageReport(coverageFileReports)
70 | }
71 |
72 | private def parseAllCoverageInfo(lines: List[String]): List[GoCoverageInfo] = {
73 | lines.collect {
74 | case regexpString(filename, lineFrom, lineTo, numberOfStatements, countOfStatements, _*) =>
75 | GoCoverageInfo(filename, lineFrom.toInt, lineTo.toInt, numberOfStatements.toInt, countOfStatements.toInt)
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/.github/workflows/create_issue_on_label.yml:
--------------------------------------------------------------------------------
1 | name: Create issue on Jira when labeled with JIRA_ISSUE_LABEL
2 |
3 | on:
4 | issues:
5 | types: [labeled]
6 |
7 | jobs:
8 | jira:
9 | env:
10 | JIRA_ISSUE_LABEL: ${{ secrets.JIRA_ISSUE_LABEL }}
11 | runs-on: ubuntu-latest
12 | steps:
13 |
14 | - name: Start workflow if GitHub issue is tagged with JIRA_ISSUE_LABEL
15 | if: github.event.label.name == env.JIRA_ISSUE_LABEL
16 | run: echo "Starting workflow"
17 |
18 | - name: Jira Login
19 | if: github.event.label.name == env.JIRA_ISSUE_LABEL
20 | id: login
21 | uses: atlassian/gajira-login@v2.0.0
22 | env:
23 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
24 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
25 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
26 |
27 | - name: Jira Create issue
28 | if: github.event.label.name == env.JIRA_ISSUE_LABEL
29 | id: create_jira_issue
30 | uses: atlassian/gajira-create@v2.0.1
31 | with:
32 | project: ${{ secrets.JIRA_PROJECT }}
33 | issuetype: ${{ secrets.JIRA_ISSUE_TYPE }}
34 | summary: "[GH#${{ github.event.issue.number }}] ${{ github.event.issue.title }}"
35 | description: |
36 | ${{ github.event.issue.body }}
37 | ----
38 | {panel}
39 | _[Github permalink |${{ github.event.issue.html_url }}]_
40 | {panel}
41 |
42 | - name: Update Jira issue if JIRA_UPDATE_ISSUE_BODY is defined
43 | if: github.event.label.name == env.JIRA_ISSUE_LABEL && env.JIRA_UPDATE_ISSUE_BODY != ''
44 | env:
45 | JIRA_UPDATE_ISSUE_BODY: ${{ secrets.JIRA_UPDATE_ISSUE_BODY }}
46 | run: >
47 | curl
48 | -u ${{ secrets.JIRA_USER_EMAIL }}:${{ secrets.JIRA_API_TOKEN }}
49 | -X PUT
50 | -H 'Content-Type: application/json'
51 | -d '${{ env.JIRA_UPDATE_ISSUE_BODY }}'
52 | ${{ secrets.JIRA_BASE_URL }}/rest/api/2/issue/${{ steps.create_jira_issue.outputs.issue }}
53 |
54 | - name: Change Title
55 | if: github.event.label.name == env.JIRA_ISSUE_LABEL
56 | uses: actions/github-script@v2.0.0
57 | env:
58 | JIRA_ISSUE_NUMBER: ${{ steps.create_jira_issue.outputs.issue }}
59 | GITHUB_ORIGINAL_TITLE: ${{ github.event.issue.title }}
60 | with:
61 | github-token: ${{secrets.GITHUB_TOKEN}}
62 | script: |
63 | const newTitle = `[${process.env.JIRA_ISSUE_NUMBER}] ${process.env.GITHUB_ORIGINAL_TITLE}`
64 | github.issues.update({
65 | issue_number: context.issue.number,
66 | owner: context.repo.owner,
67 | repo: context.repo.repo,
68 | title: newTitle
69 | })
70 |
71 | - name: Add comment after sync
72 | if: github.event.label.name == env.JIRA_ISSUE_LABEL
73 | uses: actions/github-script@v2.0.0
74 | with:
75 | github-token: ${{secrets.GITHUB_TOKEN}}
76 | script: |
77 | github.issues.createComment({
78 | issue_number: context.issue.number,
79 | owner: context.repo.owner,
80 | repo: context.repo.repo,
81 | body: 'Internal ticket created : [${{ steps.create_jira_issue.outputs.issue }}](${{ secrets.JIRA_BASE_URL }}/browse/${{ steps.create_jira_issue.outputs.issue }})'
82 | })
83 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/scala/com/codacy/parsers/PhpUnitXmlParserTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.parsers.implementation.PhpUnitXmlParser
6 | import org.scalatest.{BeforeAndAfterAll, EitherValues}
7 | import org.scalatest.wordspec.AnyWordSpec
8 | import org.scalatest.matchers.should.Matchers
9 |
10 | class PhpUnitXmlParserTest extends AnyWordSpec with BeforeAndAfterAll with Matchers with EitherValues {
11 | private val rootPath = "/home/codacy-php/"
12 | private val validReport = "coverage-parser/src/test/resources/phpunitxml/index.xml"
13 | private val incorrectReport = "coverage-parser/src/test/resources/phpunitxml/incorrect_index.xml"
14 | private val coberturaReport = "coverage-parser/src/test/resources/test_cobertura.xml"
15 | private val nonExistentReport = "coverage-parser/src/test/resources/non_existent_file.xml"
16 | private val configPhpFile = "Config.php"
17 |
18 | "parse" should {
19 | "return an invalid report" when {
20 | "report file does not exist" in {
21 | val reader = PhpUnitXmlParser.parse(new File("."), new File(nonExistentReport))
22 |
23 | reader shouldBe Symbol("left")
24 | }
25 |
26 | "report file has a different format" in {
27 | // use some coverage file that does not follow the PHPUnit xml format
28 | val reader = PhpUnitXmlParser.parse(new File("."), new File(coberturaReport))
29 |
30 | reader shouldBe Symbol("left")
31 | }
32 |
33 | "report refers to non-existent file coverage report" in {
34 | // this index contains a reference to a file that includes references to non-existent files
35 | val reader =
36 | PhpUnitXmlParser.parse(new File("."), new File(incorrectReport))
37 |
38 | reader.isLeft shouldBe true
39 | }
40 | }
41 |
42 | "verify if report is valid" in {
43 | val reader = PhpUnitXmlParser
44 | .parse(new File(rootPath), new File(validReport))
45 |
46 | reader shouldBe Symbol("right")
47 | }
48 |
49 | "return a report with the expected number of files" in {
50 | val report = PhpUnitXmlParser
51 | .parse(new File(rootPath), new File(validReport))
52 | .value
53 |
54 | report.fileReports.length shouldBe 10
55 | }
56 |
57 | "return a report with the expected file names" in {
58 | val report = PhpUnitXmlParser
59 | .parse(new File(rootPath), new File(validReport))
60 | .value
61 |
62 | report.fileReports.map(_.filename).sorted shouldBe Seq(
63 | "src/Codacy/Coverage/Api/Api.php",
64 | "src/Codacy/Coverage/CodacyPhpCoverage.php",
65 | "src/Codacy/Coverage/Config.php",
66 | "src/Codacy/Coverage/Git/GitClient.php",
67 | "src/Codacy/Coverage/Parser/CloverParser.php",
68 | "src/Codacy/Coverage/Parser/Parser.php",
69 | "src/Codacy/Coverage/Parser/PhpUnitXmlParser.php",
70 | "src/Codacy/Coverage/Report/CoverageReport.php",
71 | "src/Codacy/Coverage/Report/FileReport.php",
72 | "src/Codacy/Coverage/Report/JsonProducer.php"
73 | ).sorted
74 | }
75 |
76 | "return a report with the expected line coverage" in {
77 | val report = PhpUnitXmlParser
78 | .parse(new File(rootPath), new File(validReport))
79 | .value
80 |
81 | report.fileReports.find(_.filename.endsWith(configPhpFile)) match {
82 | case None => fail(configPhpFile + " file is not present in the list of file reports")
83 | case Some(fileReport) =>
84 | fileReport.coverage shouldBe Map(24 -> 4, 25 -> 4, 26 -> 4, 27 -> 4, 28 -> 4, 29 -> 4)
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/.github/workflows/create_issue.yml:
--------------------------------------------------------------------------------
1 | name: Create issue on Jira
2 |
3 | on:
4 | issues:
5 | types: [opened]
6 |
7 | jobs:
8 | jira:
9 | env:
10 | JIRA_CREATE_ISSUE_AUTO: ${{ secrets.JIRA_CREATE_ISSUE_AUTO }}
11 | runs-on: ubuntu-latest
12 | steps:
13 |
14 | - name: Start workflow if JIRA_CREATE_ISSUE_AUTO is enabled
15 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true'
16 | run: echo "Starting workflow"
17 |
18 | - name: Jira Login
19 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true'
20 | id: login
21 | uses: atlassian/gajira-login@v2.0.0
22 | env:
23 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
24 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
25 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
26 |
27 | - name: Jira Create issue
28 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true'
29 | id: create_jira_issue
30 | uses: atlassian/gajira-create@v2.0.1
31 | with:
32 | project: ${{ secrets.JIRA_PROJECT }}
33 | issuetype: ${{ secrets.JIRA_ISSUE_TYPE }}
34 | summary: "[GH#${{ github.event.issue.number }}] ${{ github.event.issue.title }}"
35 | description: |
36 | ${{ github.event.issue.body }}
37 | ----
38 | {panel}
39 | _[Github permalink |${{ github.event.issue.html_url }}]_
40 | {panel}
41 |
42 | - name: Update Jira issue if JIRA_UPDATE_ISSUE_BODY is defined
43 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true' && env.JIRA_UPDATE_ISSUE_BODY != ''
44 | env:
45 | JIRA_UPDATE_ISSUE_BODY: ${{ secrets.JIRA_UPDATE_ISSUE_BODY }}
46 | run: >
47 | curl
48 | -u ${{ secrets.JIRA_USER_EMAIL }}:${{ secrets.JIRA_API_TOKEN }}
49 | -X PUT
50 | -H 'Content-Type: application/json'
51 | -d '${{ env.JIRA_UPDATE_ISSUE_BODY }}'
52 | ${{ secrets.JIRA_BASE_URL }}/rest/api/2/issue/${{ steps.create_jira_issue.outputs.issue }}
53 |
54 | - name: Update GitHub issue
55 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true'
56 | uses: actions/github-script@v2.0.0
57 | env:
58 | JIRA_ISSUE_NUMBER: ${{ steps.create_jira_issue.outputs.issue }}
59 | GITHUB_ORIGINAL_TITLE: ${{ github.event.issue.title }}
60 | JIRA_ISSUE_LABEL: ${{ secrets.JIRA_ISSUE_LABEL }}
61 | with:
62 | github-token: ${{secrets.GITHUB_TOKEN}}
63 | script: |
64 | const newTitle = `[${process.env.JIRA_ISSUE_NUMBER}] ${process.env.GITHUB_ORIGINAL_TITLE}`
65 | github.issues.update({
66 | issue_number: context.issue.number,
67 | owner: context.repo.owner,
68 | repo: context.repo.repo,
69 | title: newTitle
70 | })
71 | github.issues.addLabels({
72 | issue_number: context.issue.number,
73 | owner: context.repo.owner,
74 | repo: context.repo.repo,
75 | labels: [process.env.JIRA_ISSUE_LABEL]
76 | })
77 |
78 |
79 | - name: Add comment after sync
80 | if: env.JIRA_CREATE_ISSUE_AUTO == 'true'
81 | uses: actions/github-script@v2.0.0
82 | with:
83 | github-token: ${{secrets.GITHUB_TOKEN}}
84 | script: |
85 | github.issues.createComment({
86 | issue_number: context.issue.number,
87 | owner: context.repo.owner,
88 | repo: context.repo.repo,
89 | body: 'Internal ticket created : [${{ steps.create_jira_issue.outputs.issue }}](${{ secrets.JIRA_BASE_URL }}/browse/${{ steps.create_jira_issue.outputs.issue }})'
90 | })
91 |
--------------------------------------------------------------------------------
/src/it/scala/com/codacy/CodacyCoverageReporterSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codacy
2 |
3 | import com.codacy.api.OrganizationProvider
4 | import com.codacy.configuration.parser.{BaseCommandConfig, Report}
5 | import com.codacy.di.Components
6 | import com.codacy.model.configuration.ReportConfig
7 | import com.codacy.rules.ConfigurationRules
8 | import org.scalatest.{EitherValues, Matchers, WordSpec}
9 |
10 | import java.io.File
11 |
12 | class CodacyCoverageReporterSpec extends WordSpec with Matchers with EitherValues {
13 | private val apiToken = sys.env.get("TEST_CODACY_API_TOKEN")
14 | private val projectToken = sys.env.get("TEST_CODACY_PROJECT_TOKEN")
15 | private val commitUuid = sys.env.get("TEST_COMMIT_UUID")
16 | private val projectName = sys.env.get("TEST_PROJECT_NAME")
17 | private val username = sys.env.get("TEST_USERNAME")
18 | private val apiBaseUrl = sys.env.get("TEST_BASE_API_URL")
19 |
20 | private def runCoverageReport(
21 | projectToken: Option[String],
22 | apiToken: Option[String],
23 | organizationProvider: Option[OrganizationProvider.Value],
24 | username: Option[String],
25 | projectName: Option[String],
26 | commitUuid: Option[String],
27 | sleepTime: Int,
28 | numRetries: Int
29 | ) = {
30 | val baseConfig =
31 | BaseCommandConfig(
32 | projectToken,
33 | apiToken,
34 | organizationProvider,
35 | username,
36 | projectName,
37 | apiBaseUrl,
38 | commitUuid,
39 | sleepTime,
40 | numRetries
41 | )
42 |
43 | val commandConfig = Report(
44 | baseConfig = baseConfig,
45 | language = None,
46 | coverageReports = Some(List(new File("src/test/resources/dotcover-example.xml"))),
47 | prefix = None,
48 | forceCoverageParser = None
49 | )
50 |
51 | val configRules = new ConfigurationRules(commandConfig, Map.empty)
52 | val components = new Components(configRules.validatedConfig.right.value)
53 |
54 | configRules.validatedConfig.right.value match {
55 | case config: ReportConfig => components.reportRules.codacyCoverage(config)
56 | case _ => fail("Config should be of type ReportConfig")
57 | }
58 | }
59 |
60 | "run" should {
61 | "be successful" when {
62 | "using a project token to send coverage" in {
63 | val result = runCoverageReport(projectToken, None, None, None, None, commitUuid, 10000, 3)
64 |
65 | result shouldBe 'right
66 | }
67 |
68 | "using an API token to send coverage" in {
69 | // empty projectToken so we skip project token
70 | // passing None will pick the token used for the codacy-coverage-reporter project
71 | val result =
72 | runCoverageReport(
73 | None,
74 | apiToken,
75 | Option(OrganizationProvider.gh),
76 | username,
77 | projectName,
78 | commitUuid,
79 | 10000,
80 | 3
81 | )
82 |
83 | result shouldBe 'right
84 | }
85 | }
86 |
87 | "fail" when {
88 | "project token is invalid" in {
89 | val result = runCoverageReport(Some("invalid token"), None, None, None, None, commitUuid, 10000, 3)
90 |
91 | result shouldBe 'left
92 | }
93 |
94 | "API token is invalid" in {
95 | val result = runCoverageReport(
96 | None,
97 | Some("invalid token"),
98 | Option(OrganizationProvider.gh),
99 | username,
100 | projectName,
101 | commitUuid,
102 | 10000,
103 | 3
104 | )
105 |
106 | result shouldBe 'left
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/phpunitxml/Report/FileReport.php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/api-scala/src/main/scala/com/codacy/api/client/CodacyClient.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api.client
2 |
3 | import play.api.libs.json._
4 | import com.codacy.api.util.JsonOps
5 | import scalaj.http.{Http, HttpOptions}
6 |
7 | import java.net.URL
8 | import scala.util.{Failure, Success, Try}
9 | import scala.util.control.NonFatal
10 |
11 | class CodacyClient(
12 | apiUrl: Option[String] = None,
13 | apiToken: Option[String] = None,
14 | projectToken: Option[String] = None,
15 | allowUnsafeSSL: Boolean = false
16 | ) {
17 |
18 | private case class ErrorJson(error: String)
19 | private case class PaginatedResult[T](next: Option[String], values: Seq[T])
20 |
21 | private implicit val errorJsonFormat: Reads[ErrorJson] = Json.reads[ErrorJson]
22 |
23 | private val tokens = Map.empty[String, String] ++
24 | apiToken.map(t => "api-token" -> t) ++
25 | projectToken.map(t => "project-token" -> t) ++
26 | // This is deprecated and is kept for backward compatibility. It will removed in the context of CY-1272
27 | apiToken.map(t => "api_token" -> t) ++
28 | projectToken.map(t => "project_token" -> t)
29 |
30 | private val remoteUrl = new URL(new URL(apiUrl.getOrElse("https://api.codacy.com")), "/2.0").toString()
31 |
32 | private def httpOptions = if (allowUnsafeSSL) Seq(HttpOptions.allowUnsafeSSL) else Seq.empty
33 |
34 | /*
35 | * Does an API post
36 | */
37 | def post[T](
38 | request: Request[T],
39 | value: String,
40 | timeoutOpt: Option[RequestTimeout] = None,
41 | sleepTime: Option[Int],
42 | numRetries: Option[Int]
43 | )(implicit reads: Reads[T]): RequestResponse[T] = {
44 | val url = s"$remoteUrl/${request.endpoint}"
45 | try {
46 | val headers = tokens ++ Map("Content-Type" -> "application/json")
47 |
48 | val httpRequest = timeoutOpt match {
49 | case Some(timeout) =>
50 | Http(url)
51 | .timeout(connTimeoutMs = timeout.connTimeoutMs, readTimeoutMs = timeout.readTimeoutMs)
52 | .options(httpOptions)
53 | case None => Http(url).options(httpOptions)
54 | }
55 |
56 | val body = httpRequest
57 | .params(request.queryParameters)
58 | .headers(headers)
59 | .postData(value)
60 | .asString
61 | .body
62 |
63 | parseJsonAs[T](body) match {
64 | case failure: FailedResponse =>
65 | retryPost(request, value, timeoutOpt, sleepTime, numRetries.map(x => x - 1), failure.message)
66 | case success => success
67 | }
68 | } catch {
69 | case NonFatal(ex) => retryPost(request, value, timeoutOpt, sleepTime, numRetries.map(x => x - 1), ex.getMessage)
70 | }
71 | }
72 |
73 | private def retryPost[T](
74 | request: Request[T],
75 | value: String,
76 | timeoutOpt: Option[RequestTimeout],
77 | sleepTime: Option[Int],
78 | numRetries: Option[Int],
79 | failureMessage: String
80 | )(implicit reads: Reads[T]): RequestResponse[T] = {
81 | if (numRetries.exists(x => x > 0)) {
82 | sleepTime.map(x => Thread.sleep(x))
83 | post(request, value, timeoutOpt, sleepTime, numRetries.map(x => x - 1))
84 | } else {
85 | RequestResponse.failure(
86 | s"Error doing a post to $remoteUrl/${request.endpoint}: exhausted retries due to $failureMessage"
87 | )
88 | }
89 | }
90 |
91 | private def parseJsonAs[T](input: String)(implicit reads: Reads[T]): RequestResponse[T] = {
92 | parseJson(input) match {
93 | case failure: FailedResponse => failure
94 | case SuccessfulResponse(json) =>
95 | json
96 | .validate[T]
97 | .fold(
98 | errors => FailedResponse(JsonOps.handleConversionFailure(errors)),
99 | converted => SuccessfulResponse(converted)
100 | )
101 | }
102 | }
103 |
104 | private def parseJson(input: String): RequestResponse[JsValue] = {
105 | Try(Json.parse(input)) match {
106 | case Success(json) =>
107 | json
108 | .validate[ErrorJson]
109 | .fold(_ => SuccessfulResponse(json), apiError => FailedResponse(s"API Error: ${apiError.error}"))
110 | case Failure(exception) =>
111 | FailedResponse(s"Failed to parse API response as JSON: $input\nUnderlying exception - ${exception.getMessage}")
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Codacy Coverage Reporter
2 |
3 | [](https://www.codacy.com/gh/codacy/codacy-coverage-reporter/dashboard?utm_source=github.com&utm_medium=referral&utm_content=codacy/codacy-coverage-reporter&utm_campaign=Badge_Grade)
4 | [](https://circleci.com/gh/codacy/codacy-coverage-reporter)
5 | [](https://maven-badges.herokuapp.com/maven-central/com.codacy/codacy-coverage-reporter)
6 |
7 | Multi-language coverage reporter for Codacy https://www.codacy.com
8 |
9 | ## Setup
10 |
11 | Follow the instructions on how to [add coverage to your repository](https://docs.codacy.com/coverage-reporter/adding-coverage-to-your-repository/).
12 |
13 | If necessary, see [alternative ways of running Codacy Coverage Reporter](https://docs.codacy.com/coverage-reporter/alternative-ways-of-running-coverage-reporter/) for other ways of running Codacy Coverage Reporter, such as by installing the binary manually or using a CircleCI Orb or the [Codacy Coverage Reporter GitHub Action](https://github.com/codacy/codacy-coverage-reporter-action).
14 |
15 | For a complete list of commands and options, run the Codacy Coverage Reporter with the flag `--help`. For example:
16 |
17 | ```
18 | $ bash <(curl -Ls https://coverage.codacy.com/get.sh) report --help
19 | ______ __
20 | / ____/___ ____/ /___ ________ __
21 | / / / __ \/ __ / __ `/ ___/ / / /
22 | / /___/ /_/ / /_/ / /_/ / /__/ /_/ /
23 | \____/\____/\__,_/\__,_/\___/\__, /
24 | /____/
25 |
26 | Codacy Coverage Reporter
27 |
28 | --> Using codacy reporter codacy-coverage-reporter-linux from cache
29 | Command: report
30 | Usage: codacy-coverage-reporter report
31 | --project-token | -t
32 | --api-token | -a
33 | --organization-provider (manual, gh, bb, ghe, bbe, gl, gle)
34 | --username | -u
35 | --project-name | -p
36 | --codacy-api-base-url
37 | --commit-uuid
38 | --http-timeout
39 | --skip | -s
40 | --sleep-time
41 | --num-retries
42 | --language | -l
43 | --coverage-reports | -r
44 | --partial
45 | --prefix
46 | --skip-ssl-verification [default: false] - Skip the SSL certificate verification when communicating with the Codacy API
47 | --force-coverage-parser
48 | Available parsers are: opencover,clover,lcov,phpunit,jacoco,dotcover,cobertura,go
49 |
50 |
51 | --> Succeeded!
52 | ```
53 |
54 | Codacy Coverage Reporter can be run with the `CODACY_REPORTER_OPTIONS` environment variable. This is useful for passing options to the reporter without having to modify the script.
55 |
56 | ```
57 | CODACY_REPORTER_OPTIONS="--skip-ssl-verification true"
58 | ```
59 |
60 | ## What is Codacy?
61 |
62 | [Codacy](https://www.codacy.com/) is an Automated Code Review Tool that monitors your technical debt, helps you improve your code quality, teaches best practices to your developers, and helps you save time in Code Reviews.
63 |
64 | ### Among Codacy’s features:
65 |
66 | - Identify new Static Analysis issues
67 | - Commit and Pull Request Analysis with GitHub, GitLab, and Bitbucket
68 | - Auto-comments on Commits and Pull Requests
69 | - Integrations with Slack and Jira
70 | - Track issues in Code Style, Security, Error Proneness, Performance, Unused Code and other categories
71 |
72 | Codacy also helps keep track of Code Coverage, Code Duplication, and Code Complexity.
73 |
74 | Codacy supports PHP, Python, Ruby, Java, JavaScript, and Scala, among others.
75 |
76 | ### Free for Open Source
77 |
78 | Codacy is free for Open Source projects.
79 |
--------------------------------------------------------------------------------
/.github/workflows/comment_issue.yml:
--------------------------------------------------------------------------------
1 | name: Comment issue on Jira
2 |
3 | on:
4 | issue_comment:
5 | types: [created]
6 |
7 | jobs:
8 | jira:
9 | env:
10 | JIRA_CREATE_COMMENT_AUTO: ${{ secrets.JIRA_CREATE_COMMENT_AUTO }}
11 | runs-on: ubuntu-latest
12 | steps:
13 |
14 | - name: Start workflow if JIRA_CREATE_COMMENT_AUTO is enabled
15 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true'
16 | run: echo "Starting workflow"
17 |
18 | - name: Check GitHub Issue type
19 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true'
20 | id: github_issue_type
21 | uses: actions/github-script@v2.0.0
22 | with:
23 | result-encoding: string
24 | script: |
25 | // An Issue can be a pull request, you can identify pull requests by the pull_request key
26 | const pullRequest = ${{ toJson(github.event.issue.pull_request) }}
27 | if(pullRequest) {
28 | return "pull-request"
29 | } else {
30 | return "issue"
31 | }
32 |
33 | - name: Check if GitHub Issue has JIRA_ISSUE_LABEL
34 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true'
35 | id: github_issue_has_jira_issue_label
36 | uses: actions/github-script@v2.0.0
37 | env:
38 | JIRA_ISSUE_LABEL: ${{ secrets.JIRA_ISSUE_LABEL }}
39 | with:
40 | result-encoding: string
41 | script: |
42 | const labels = ${{ toJson(github.event.issue.labels) }}
43 | if(labels.find(label => label.name == process.env.JIRA_ISSUE_LABEL)) {
44 | return "true"
45 | } else {
46 | return "false"
47 | }
48 |
49 | - name: Continue workflow only for Issues (not Pull Requests) tagged with JIRA_ISSUE_LABEL
50 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' && env.GITHUB_ISSUE_TYPE == 'issue' && env.GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL == 'true'
51 | env:
52 | GITHUB_ISSUE_TYPE: ${{ steps.github_issue_type.outputs.result }}
53 | GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL: ${{ steps.github_issue_has_jira_issue_label.outputs.result }}
54 | run: echo "GitHub Issue is tracked on Jira, eligilbe to be commented"
55 |
56 | - name: Jira Login
57 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' && env.GITHUB_ISSUE_TYPE == 'issue' && env.GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL == 'true'
58 | id: login
59 | uses: atlassian/gajira-login@v2.0.0
60 | env:
61 | GITHUB_ISSUE_TYPE: ${{ steps.github_issue_type.outputs.result }}
62 | GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL: ${{ steps.github_issue_has_jira_issue_label.outputs.result }}
63 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
64 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
65 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
66 |
67 | - name: Extract Jira number
68 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' && env.GITHUB_ISSUE_TYPE == 'issue' && env.GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL == 'true'
69 | id: extract_jira_number
70 | uses: actions/github-script@v2.0.0
71 | env:
72 | GITHUB_ISSUE_TYPE: ${{ steps.github_issue_type.outputs.result }}
73 | GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL: ${{ steps.github_issue_has_jira_issue_label.outputs.result }}
74 | JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }}
75 | GITHUB_TITLE: ${{ github.event.issue.title }}
76 | with:
77 | script: |
78 | const jiraTaskRegex = new RegExp(`\\\[(${process.env.JIRA_PROJECT}-[0-9]+?)\\\]`)
79 | return process.env.GITHUB_TITLE.match(jiraTaskRegex)[1]
80 | result-encoding: string
81 |
82 | - name: Jira Add comment on issue
83 | if: env.JIRA_CREATE_COMMENT_AUTO == 'true' && env.GITHUB_ISSUE_TYPE == 'issue' && env.GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL == 'true'
84 | id: add_comment_jira_issue
85 | uses: atlassian/gajira-comment@v2.0.2
86 | env:
87 | GITHUB_ISSUE_TYPE: ${{ steps.github_issue_type.outputs.result }}
88 | GITHUB_ISSUE_HAS_JIRA_ISSUE_LABEL: ${{ steps.github_issue_has_jira_issue_label.outputs.result }}
89 | with:
90 | issue: ${{ steps.extract_jira_number.outputs.result }}
91 | comment: |
92 | GitHub Comment : ${{ github.event.comment.user.login }}
93 | {quote}${{ github.event.comment.body }}{quote}
94 | ----
95 | {panel}
96 | _[Github permalink |${{ github.event.comment.html_url }}]_
97 | {panel}
98 |
--------------------------------------------------------------------------------
/coverage-parser/src/main/scala/com/codacy/parsers/implementation/CloverParser.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers.implementation
2 |
3 | import java.io.File
4 | import java.nio.file.Paths
5 |
6 | import com.codacy.api.{CoverageFileReport, CoverageReport}
7 | import com.codacy.parsers.util.TextUtils
8 | import com.codacy.parsers.{CoverageParser, XmlReportParser}
9 |
10 | import scala.xml.{Elem, Node, NodeSeq}
11 |
12 | object CloverParser extends CoverageParser with XmlReportParser {
13 | private val CoverageTag = "coverage"
14 | private val ProjectTag = "project"
15 | private val MetricsTag = "metrics"
16 |
17 | override val name: String = "Clover"
18 |
19 | override def parse(rootProject: File, reportFile: File): Either[String, CoverageReport] = {
20 | parseReport(reportFile, s"Could not find tag hierarchy <$CoverageTag> <$ProjectTag> <$MetricsTag> tags") { node =>
21 | parseReportNode(rootProject, node)
22 | }
23 | }
24 |
25 | override def validateSchema(xml: Elem): Boolean = (xml \\ CoverageTag \ ProjectTag \ MetricsTag).nonEmpty
26 |
27 | override def getRootNode(xml: Elem): NodeSeq = xml \\ CoverageTag
28 |
29 | private def parseReportNode(rootProject: File, report: NodeSeq): Either[String, CoverageReport] = {
30 | val rootPath = TextUtils.sanitiseFilename(rootProject.getAbsolutePath)
31 |
32 | val coverageFiles = (report \\ "file").foldLeft[Either[String, Seq[CoverageFileReport]]](Right(List())) {
33 | case (Right(accumulatedFileReports), fileTag) =>
34 | val fileReport = getCoverageFileReport(rootPath, fileTag)
35 | fileReport.map(_ +: accumulatedFileReports)
36 |
37 | case (Left(errorMessage), _) => Left(errorMessage)
38 | }
39 |
40 | for {
41 | coverageFiles <- coverageFiles
42 | } yield CoverageReport(coverageFiles)
43 | }
44 |
45 | private def getCoverageFileReport(rootPath: String, fileNode: Node): Either[String, CoverageFileReport] = {
46 | val filePath = getUnixPathAttribute(fileNode, "path")
47 | val filename = getUnixPathAttribute(fileNode, "name")
48 |
49 | val relativeFilePath = filePath
50 | .orElse(filename)
51 | .fold[Either[String, String]] {
52 | Left("Could not read file path due to missing 'path' and 'name' attributes in the report file tag.")
53 | } {
54 | case path if Paths.get(path).isAbsolute =>
55 | Right(path.stripPrefix(rootPath).stripPrefix("/"))
56 |
57 | case path =>
58 | Right(path)
59 | }
60 |
61 | for {
62 | relativeFilePath <- relativeFilePath
63 | linesCoverage <- getLinesCoverage(fileNode).left
64 | .map(errorMessage => s"Could not retrieve lines coverage for file '$relativeFilePath': $errorMessage")
65 | } yield CoverageFileReport(relativeFilePath, linesCoverage)
66 | }
67 |
68 | private def getLinesCoverage(fileNode: Node): Either[String, Map[Int, Int]] = {
69 | val fileLineTags = (fileNode \ "line")
70 |
71 | fileLineTags.foldLeft[Either[String, Map[Int, Int]]](Right(Map.empty[Int, Int])) {
72 | case (left: Left[_, _], _) => left
73 |
74 | case (Right(lines), line)
75 | if ((line \@ "type") == "stmt" || (line \@ "type") == "cond") && (line \@ "count").nonEmpty =>
76 | val lineCoverage = for {
77 | lineNumber <- getFirstNonEmptyValueAsInt(Seq(line), "num")
78 | countOfExecutions <- getFirstNonEmptyValueAsInt(Seq(line), "count")
79 | } yield (lineNumber, countOfExecutions)
80 | lineCoverage.map(lines + _)
81 |
82 | case (accumulated, _) => accumulated
83 | }
84 | }
85 |
86 | /* Retrieves the attribute with name @attributeName from @node,
87 | * converts the contents to string and converts path to unix style
88 | */
89 | private def getUnixPathAttribute(node: Node, attributeName: String): Option[String] = {
90 | node.attribute(attributeName).flatMap(_.headOption.map(_.text)).map(TextUtils.sanitiseFilename)
91 | }
92 |
93 | /* Retrieves the first non-empty value in attribute with name @attributeName from @nodes
94 | * and converts the value to a number
95 | */
96 | private def getFirstNonEmptyValueAsInt(nodes: Seq[Node], attributeName: String): Either[String, Int] = {
97 | val attributeValues = nodes
98 | .flatMap(_.attribute(attributeName).getOrElse(Seq.empty[Node]))
99 |
100 | val firstNonEmptyValue = attributeValues
101 | .map(_.text.trim)
102 | .find(_.nonEmpty)
103 |
104 | val result = firstNonEmptyValue.fold[Either[String, String]](
105 | Left(s"Could not find attribute with name '$attributeName'")
106 | )(Right(_))
107 |
108 | result.flatMap { attributeValue =>
109 | parseInt(attributeValue).left.flatMap(_ => Left(s"Value of attribute with name '$attributeName' is not a number"))
110 | }
111 | }
112 |
113 | /* Parses string value into a number */
114 | private def parseInt(value: String): Either[String, Int] = {
115 | try {
116 | Right(value.toInt)
117 | } catch {
118 | case _: NumberFormatException =>
119 | Left(s"Value '$value' is not a number")
120 | }
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/phpunitxml/Parser/PhpUnitXmlParser.php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/src/main/scala/com/codacy/rules/commituuid/CommitUUIDProvider.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.rules.commituuid
2 |
3 | import java.io.File
4 |
5 | import com.codacy.rules.commituuid.providers._
6 | import com.codacy.model.configuration.CommitUUID
7 | import wvlet.log.LogSupport
8 |
9 | import com.codacy.api.helpers.vcs.{CommitInfo, GitClient}
10 | import scala.util.{Failure, Success}
11 |
12 | /**
13 | * Trait for a provider that can get a [[CommitUUID]] from environment variables.
14 | */
15 | trait CommitUUIDProvider {
16 |
17 | /** Name of the provider */
18 | val name: String
19 |
20 | /** Default error message */
21 | protected val commitNotFoundMessage = s"Can't find $name commit SHA-1 hash in the environment."
22 |
23 | protected val commitNotValidMessage =
24 | "Commit SHA-1 hash isn't valid. Make sure it consists of 40 hexadecimal characters."
25 |
26 | /** Parses `environmentVariable` as a [[CommitUUID]], validates it and returns it, as a [[Right]].
27 | *
28 | * If the parsing or validation fail, return an error message, as a [[Left]].
29 | *
30 | * @param environmentVariable An option for the value of an environment variable.
31 | * @return Commit uuid or an error message
32 | */
33 | protected def parseEnvironmentVariable(environmentVariable: Option[String]): Either[String, CommitUUID] =
34 | environmentVariable match {
35 | case Some(commitUUID) => CommitUUID.fromString(commitUUID)
36 | case None => Left(commitNotFoundMessage)
37 | }
38 |
39 | /** Validates if the environment has the expected variables for this provider.
40 | *
41 | * @param environment environment variables.
42 | * @return `true` if all variables are defined, `false` otherwise.
43 | */
44 | def validateEnvironment(environment: Map[String, String]): Boolean
45 |
46 | /** Gets a **valid** commit UUID, as [[Right]], from the environment if one exists under the
47 | * expected name and [[CommitUUID.isValid is valid]].
48 | * Otherwise, get an error message, as [[Left]], to present to the user.
49 | *
50 | * @param environment environment variables.
51 | * @return Either a **valid** commit UUID or an error message.
52 | */
53 | def getValidCommitUUID(environment: Map[String, String]): Either[String, CommitUUID]
54 | }
55 |
56 | /**
57 | * Provides a commit UUID
58 | *
59 | * This object provides a commit uuid from various providers
60 | */
61 | object CommitUUIDProvider extends LogSupport {
62 |
63 | private val providers = List(
64 | AppveyorProvider,
65 | ArgoCDProvider,
66 | AWSCodeBuildProvider,
67 | AzurePipelinesProvider,
68 | BitbucketCloudProvider,
69 | BitriseCIProvider,
70 | BuildkiteCIProvider,
71 | CircleCIProvider,
72 | CodefreshCIProvider,
73 | CodeshipCIProvider,
74 | DockerProvider,
75 | DroneCIProvider,
76 | GitHubActionProvider,
77 | GitlabProvider,
78 | GreenhouseCIProvider,
79 | HerokuCIProvider,
80 | JenkinsProvider,
81 | MagnumCIProvider,
82 | SemaphoreCIProvider,
83 | ShippableCIProvider,
84 | SolanoCIProvider,
85 | TeamCityProvider,
86 | TravisCIProvider,
87 | WerckerCIProvider
88 | )
89 |
90 | /** Try to get a commit UUID from all available providers.
91 | *
92 | * @param environment environment variables.
93 | * @return Either a commit uuid or an error message.
94 | */
95 | def getFromAll(environment: Map[String, String]): Either[String, CommitUUID] = {
96 | getFromEnvironment(environment) match {
97 | case Left(msg) =>
98 | logger.info(msg)
99 | logger.info("Trying to get commit SHA-1 hash from local Git directory")
100 | getLatestFromGit()
101 | case uuid => uuid
102 | }
103 | }
104 |
105 | /** Gets the latest commit uuid from the local git directory.
106 | *
107 | * @return Either a commit uuid or an error message.
108 | */
109 | def getLatestFromGit(): Either[String, CommitUUID] = {
110 | val currentPath = new File(System.getProperty("user.dir"))
111 | new GitClient(currentPath).latestCommitInfo match {
112 | case Failure(e) =>
113 | Left("Commit SHA-1 hash not provided and could not retrieve it from local Git directory")
114 | case Success(CommitInfo(uuid, authorName, authorEmail, date)) =>
115 | val info =
116 | s"""Commit SHA-1 hash not provided, using latest commit of local Git directory:
117 | |$uuid $authorName <$authorEmail> $date""".stripMargin
118 |
119 | logger.info(info)
120 | CommitUUID.fromString(uuid)
121 | }
122 | }
123 |
124 | /** Get a commit UUID from the first provider that can find a valid commit UUID in `environment`.
125 | *
126 | * @param environment environment variables.
127 | *
128 | * @see [[providers]] to check the order for which providers are tested.
129 | */
130 | def getFromEnvironment(environment: Map[String, String]): Either[String, CommitUUID] = {
131 |
132 | val validUUID = providers.collectFirst {
133 | case provider if provider.validateEnvironment(environment) && provider.getValidCommitUUID(environment).isRight =>
134 | val uuid = provider.getValidCommitUUID(environment)
135 | uuid.foreach(u => logger.info(s"CI/CD provider ${provider.name} found Commit UUID ${u.value}"))
136 | uuid
137 | }
138 |
139 | validUUID
140 | .toRight("Can't find or validate commit SHA-1 hash from any supported CI/CD provider.")
141 | .flatMap(identity)
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/scala/com/codacy/parsers/CloverParserTest.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.parsers
2 |
3 | import java.io.File
4 |
5 | import com.codacy.parsers.implementation.CloverParser
6 | import org.scalatest.EitherValues
7 | import org.scalatest.matchers.should.Matchers
8 | import org.scalatest.wordspec.AnyWordSpec
9 |
10 | class CloverParserTest extends AnyWordSpec with Matchers with EitherValues {
11 |
12 | "parse" should {
13 |
14 | "fail to parse an invalid report" when {
15 |
16 | "the report file does not exist" in {
17 | // Arrange
18 | val nonExistentReportPath = "coverage-parser/src/test/resources/non-existent.xml"
19 |
20 | // Act
21 | val parseResult = CloverParser.parse(new File("."), new File(nonExistentReportPath))
22 |
23 | // Assert
24 | val error = parseResult.left.value
25 | error should startWith("Unparseable report. ")
26 | error should endWith("coverage-parser/src/test/resources/non-existent.xml (No such file or directory)")
27 | }
28 |
29 | "the report is not in the Clover format" in {
30 | // Arrange
31 | val reportNotInCloverFormat = "coverage-parser/src/test/resources/test_cobertura.xml"
32 |
33 | // Act
34 | val parseResult = CloverParser.parse(new File("."), new File(reportNotInCloverFormat))
35 |
36 | // Assert
37 | parseResult shouldBe Left("Invalid report. Could not find tag hierarchy tags.")
38 | }
39 | }
40 |
41 | val cloverReportPath = "coverage-parser/src/test/resources/test_clover.xml"
42 | val cloverWithoutPackagesFilePath = "coverage-parser/src/test/resources/test_clover_without_packages.xml"
43 |
44 | "succeed to parse a valid report" when {
45 |
46 | "the report has packages" in {
47 | // Act
48 | val parseResult = CloverParser.parse(new File("."), new File(cloverReportPath))
49 |
50 | // Assert
51 | parseResult shouldBe Symbol("right")
52 | }
53 |
54 | "the report does not have packages" in {
55 | // Act
56 | val parseResult = CloverParser
57 | .parse(new File("/home/codacy-php/"), new File(cloverWithoutPackagesFilePath))
58 |
59 | // Assert
60 | parseResult shouldBe Symbol("right")
61 | }
62 |
63 | }
64 |
65 | "parse correct file paths" when {
66 |
67 | "reports contain both name and path attributes in file tags" in {
68 | // Arrange
69 | val cloverWithPaths = new File("coverage-parser/src/test/resources/test_clover_with_paths.xml")
70 |
71 | // Act
72 | val parsedReportFilePaths =
73 | CloverParser
74 | .parse(new File("/Users/username/workspace/repository"), cloverWithPaths)
75 | .value
76 | .fileReports
77 | .map(_.filename)
78 |
79 | // Assert
80 | parsedReportFilePaths should contain("src/app/file.js")
81 | }
82 |
83 | }
84 |
85 | "return the same report with or without packages" in {
86 | // Act
87 | val parseResultWithoutPackages = CloverParser
88 | .parse(new File("/home/codacy-php/"), new File(cloverWithoutPackagesFilePath))
89 | val parseResultWithPackages =
90 | CloverParser.parse(new File("/home/codacy-php/"), new File(cloverReportPath))
91 |
92 | // Assert
93 | parseResultWithoutPackages shouldBe parseResultWithPackages
94 | }
95 |
96 | "return a report with the expected number of files" in {
97 | // Arrange
98 | val expectedNumberOfFiles = 5
99 |
100 | // Act
101 | val fileReports =
102 | CloverParser.parse(new File("/home/codacy-php/"), new File(cloverReportPath)).value.fileReports
103 |
104 | // Assert
105 | fileReports should have length expectedNumberOfFiles
106 | }
107 |
108 | "return a report with the expected relative file paths" in {
109 | // Arrange
110 | val expectedFilePaths = Seq(
111 | "src/Codacy/Coverage/Parser/Parser.php",
112 | "src/Codacy/Coverage/Report/CoverageReport.php",
113 | "vendor/sebastian/global-state/src/Blacklist.php",
114 | "vendor/sebastian/global-state/src/Restorer.php",
115 | "vendor/sebastian/global-state/src/Snapshot.php"
116 | )
117 |
118 | // Act
119 | val parsedReportFilePaths =
120 | CloverParser
121 | .parse(new File("/home/codacy-php/"), new File(cloverReportPath))
122 | .value
123 | .fileReports
124 | .map(_.filename)
125 |
126 | // Assert
127 | parsedReportFilePaths should contain theSameElementsAs expectedFilePaths
128 | }
129 |
130 | "return a report with the expected file line coverage" in {
131 | // Arrange
132 | val filePath = "src/Codacy/Coverage/Report/CoverageReport.php"
133 |
134 | // Act
135 | val fileLineCoverage =
136 | CloverParser
137 | .parse(new File("/home/codacy-php/"), new File(cloverReportPath))
138 | .value
139 | .fileReports
140 | .find(_.filename == filePath)
141 | .getOrElse(fail(s"Could not find report for file:$filePath"))
142 | .coverage
143 |
144 | // Assert
145 | fileLineCoverage shouldBe Map(
146 | 11 -> 1,
147 | 12 -> 1,
148 | 13 -> 1,
149 | 16 -> 1,
150 | 19 -> 0,
151 | 30 -> 0,
152 | 31 -> 0,
153 | 32 -> 0,
154 | 33 -> 0,
155 | 36 -> 0,
156 | 39 -> 0,
157 | 42 -> 0
158 | )
159 | }
160 |
161 | }
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/api-scala/src/main/scala/com/codacy/api/service/CoverageServices.scala:
--------------------------------------------------------------------------------
1 | package com.codacy.api.service
2 |
3 | import com.codacy.api.client.{CodacyClient, Request, RequestResponse, RequestSuccess, RequestTimeout}
4 | import com.codacy.api.{CoverageReport, OrganizationProvider}
5 | import play.api.libs.json.Json
6 |
7 | class CoverageServices(client: CodacyClient) {
8 |
9 | /**
10 | * Send coverage report to Codacy endpoint.
11 | * This endpoint requires a project token to authenticate the request and identify the project.
12 | * Therefore, the client must be initialized with a valid project token.
13 | * @param commitUuid commit unique identifier
14 | * @param language programing language
15 | * @param coverageReport coverage report being sent to Codacy
16 | * @param partial flag that signals if the report operation will be broken in multiple operations
17 | * @param timeoutOpt socket connection and read timeouts in milliseconds
18 | * @return Request response
19 | */
20 | def sendReport(
21 | commitUuid: String,
22 | language: String,
23 | coverageReport: CoverageReport,
24 | partial: Boolean = false,
25 | timeoutOpt: Option[RequestTimeout] = None,
26 | sleepTime: Option[Int] = None,
27 | numRetries: Option[Int] = None,
28 | ): RequestResponse[RequestSuccess] = {
29 | val endpoint = s"coverage/$commitUuid/${encodePathSegment(language.toLowerCase)}"
30 |
31 | postRequest(endpoint, coverageReport, partial, timeoutOpt, sleepTime, numRetries)
32 | }
33 |
34 | /**
35 | * Send final notification signaling the end of the report operation.
36 | * This endpoint requires an account token to authenticate the request and identify the project.
37 | * Therefore, the client must be initialized with a valid account token.
38 | * @param commitUuid commit unique identifier
39 | * @param timeoutOpt socket connection and read timeouts in milliseconds
40 | * @return Request Response
41 | */
42 | def sendFinalNotification(
43 | commitUuid: String,
44 | timeoutOpt: Option[RequestTimeout] = None,
45 | sleepTime: Option[Int] = None,
46 | numRetries: Option[Int] = None,
47 | ): RequestResponse[RequestSuccess] = {
48 | val endpoint = s"commit/$commitUuid/coverageFinal"
49 |
50 | postEmptyRequest(endpoint, timeoutOpt, sleepTime, numRetries)
51 | }
52 |
53 | /**
54 | * Send coverage report with a project name to Codacy endpoint.
55 | * This endpoint requires an account token to authenticate the request.
56 | * Therefore, the client must be initialized with a valid account token.
57 | * @param username reporter's username
58 | * @param projectName name of the project the report pertains
59 | * @param commitUuid commit unique identifier
60 | * @param language programing language
61 | * @param coverageReport coverage report being reported
62 | * @param partial flag that signals if the report operation will be broken in multiple operations
63 | * @param timeoutOpt socket connection and read timeouts in milliseconds
64 | * @return Request Response
65 | */
66 | def sendReportWithProjectName(
67 | provider: OrganizationProvider.Value,
68 | username: String,
69 | projectName: String,
70 | commitUuid: String,
71 | language: String,
72 | coverageReport: CoverageReport,
73 | partial: Boolean = false,
74 | timeoutOpt: Option[RequestTimeout] = None,
75 | sleepTime: Option[Int] = None,
76 | numRetries: Option[Int] = None,
77 | ): RequestResponse[RequestSuccess] = {
78 | val endpoint =
79 | s"${provider.toString}/$username/$projectName/commit/$commitUuid/coverage/${encodePathSegment(language.toLowerCase)}"
80 | postRequest(endpoint, coverageReport, partial, timeoutOpt, sleepTime, numRetries)
81 | }
82 |
83 | /**
84 | * Send final notification with a project name, signaling the end of the report operation.
85 | * This endpoint requires an account token to authenticate the request.
86 | * Therefore, the client must be initialized with a valid account token.
87 | * @param username reporter's username
88 | * @param projectName name of the project the report pertains
89 | * @param commitUuid commit unique identifier
90 | * @param timeoutOpt socket connection and read timeouts in milliseconds
91 | * @return Request Response
92 | */
93 | def sendFinalWithProjectName(
94 | provider: OrganizationProvider.Value,
95 | username: String,
96 | projectName: String,
97 | commitUuid: String,
98 | timeoutOpt: Option[RequestTimeout] = None,
99 | sleepTime: Option[Int] = None,
100 | numRetries: Option[Int] = None
101 | ): RequestResponse[RequestSuccess] = {
102 | val endpoint = s"${provider.toString}/$username/$projectName/commit/$commitUuid/coverageFinal"
103 |
104 | postEmptyRequest(endpoint, timeoutOpt, sleepTime, numRetries)
105 | }
106 |
107 | private def postRequest(
108 | endpoint: String,
109 | coverageReport: CoverageReport,
110 | partial: Boolean,
111 | timeoutOpt: Option[RequestTimeout],
112 | sleepTime: Option[Int],
113 | numRetries: Option[Int]
114 | ) = {
115 | val queryParams = getQueryParameters(partial)
116 |
117 | val jsonString = serializeCoverageReport(coverageReport)
118 |
119 | client.post(Request(endpoint, classOf[RequestSuccess], queryParams), jsonString, timeoutOpt, sleepTime, numRetries)
120 | }
121 |
122 | private def postEmptyRequest(
123 | endpoint: String,
124 | timeoutOpt: Option[RequestTimeout],
125 | sleepTime: Option[Int],
126 | numRetries: Option[Int]
127 | ) =
128 | client.post(Request(endpoint, classOf[RequestSuccess]), "{}", timeoutOpt, sleepTime, numRetries)
129 |
130 | private def getQueryParameters(partial: Boolean) = {
131 | Map("partial" -> partial.toString)
132 | }
133 | private def serializeCoverageReport(coverageReport: CoverageReport) =
134 | Json.stringify(Json.toJson(coverageReport))
135 |
136 | /**
137 | * Any encoding that we do here, needs to have the same output
138 | * of play.utils.UriEncoding.encodePathSegment for our languages.
139 | * https://github.com/playframework/playframework/blob/316fbd61c9fc6a6081a3aeef7e773c8bbccd0b6b/core/play/src/main/scala/play/utils/UriEncoding.scala#L50
140 | */
141 | private def encodePathSegment(segment: String): String = segment.replaceAll(" ", "%20")
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/src/test/resources/non-git-report.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/test/resources/dotcover-example.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/coverage-parser/src/test/resources/test_dotcover.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------