From a487dc5eb8edaa66287654e489d74d8c795b43c6 Mon Sep 17 00:00:00 2001 From: "A. G" Date: Mon, 5 Nov 2018 20:18:40 +0000 Subject: [PATCH] new acton: loadApexCodeCoverageAggregate allows to query from SFDC - coverage data for specific Class/Trigger --- .../com/neowit/apex/actions/Action.scala | 1 + .../LoadApexCodeCoverageAggregate.scala | 102 ++++++++++++++++++ .../actions/tooling/RunTestConversions.scala | 34 ++++-- .../actions/tooling/RunTestJsonSupport.scala | 13 +++ .../com/neowit/response/BaseResult.scala | 1 + .../protocols/vim/ResponseWriterVim.scala | 1 + .../response/protocols/vim/RunTests.scala | 10 ++ 7 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 src/main/scala/com/neowit/apex/actions/tooling/LoadApexCodeCoverageAggregate.scala diff --git a/src/main/scala/com/neowit/apex/actions/Action.scala b/src/main/scala/com/neowit/apex/actions/Action.scala index e17e99b7..426e98d3 100644 --- a/src/main/scala/com/neowit/apex/actions/Action.scala +++ b/src/main/scala/com/neowit/apex/actions/Action.scala @@ -38,6 +38,7 @@ object ActionFactory { "saveModified" -> "com.neowit.apex.actions.tooling.SaveModified", "saveSpecificFiles" -> "com.neowit.apex.actions.tooling.SaveSpecificFiles", "runTestsTooling" -> "com.neowit.apex.actions.tooling.RunTests", + "loadApexCodeCoverageAggregate" -> "com.neowit.apex.actions.tooling.LoadApexCodeCoverageAggregate", "deployModified" -> "DeployModified", "deployAll" -> "DeployAll", "deploySpecificFiles" -> "DeploySpecificFiles", diff --git a/src/main/scala/com/neowit/apex/actions/tooling/LoadApexCodeCoverageAggregate.scala b/src/main/scala/com/neowit/apex/actions/tooling/LoadApexCodeCoverageAggregate.scala new file mode 100644 index 00000000..6d48cfea --- /dev/null +++ b/src/main/scala/com/neowit/apex/actions/tooling/LoadApexCodeCoverageAggregate.scala @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018 Andrey Gavrikov. + * this file is part of tooling-force.com application + * https://github.com/neowit/tooling-force.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.neowit.apex.actions.tooling + +import com.neowit.apex._ +import com.neowit.apex.actions._ +import com.neowit.utils.FileUtils + +import scala.concurrent.{ExecutionContext, Future} + +/** + * Created by Andrey Gavrikov + */ +class LoadApexCodeCoverageAggregate extends ApexActionWithReadOnlySession with RunTestJsonSupport { + + override def getHelp: ActionHelp = new ActionHelp { + override def getExample: String = "" + + override def getParamDescription(paramName: String): String = paramName match { + case "classOrTriggerName" => "Name of Class or Trigger for which to load coverage" + case x => "Unsupported parameter: " + x + } + + override def getParamNames: List[String] = List() + + override def getSummary: String = "Get code coverage" + + override def getName: String = "loadApexCodeCoverageAggregate" + } + + override protected def act()(implicit ec: ExecutionContext): Future[ActionResult] = { + + val actionResult = config.getProperty("classOrTriggerName") match { + case Some(classOrTriggerNameProvided) => + var classOrTriggerName = FileUtils.removeExtension(classOrTriggerNameProvided) + val queryIterator = SoqlQuery.getQueryIteratorTooling(session, + s""" select ApexClassOrTrigger.Name, ApexClassOrTriggerId, NumLinesCovered, NumLinesUncovered, Coverage + | from ApexCodeCoverageAggregate + | where ApexClassOrTrigger.Name = '$classOrTriggerName' + |""".stripMargin) + + var errorBuilder = Array.newBuilder[String] + + val coverageResultOpt = + if (queryIterator.hasNext) { + val record = queryIterator.map(obj => obj.convertTo[RunTestJsonSupport.ApexCodeCoverageAggregate]).next() + println(record) + val coverageResult = RunTestConversions.toCodeCoverageResult(record) + Option(coverageResult) + } else { + errorBuilder += "No coverage available for " + classOrTriggerName + None + } + + val runTestResult = new com.neowit.apex.RunTestsResult { + override def getCodeCoverage: Array[CodeCoverageResult] = { + coverageResultOpt match { + case Some(coverageResult) => + List(new RunTests.CodeCoverageResultTooling(coverageResult)).toArray + case None => Array() + } + } + + override def getCodeCoverageWarnings: Array[CodeCoverageWarning] = Array() + + override def getFailures: Array[RunTestFailure] = Array() + } + + val errors = errorBuilder.result() + if (errors.nonEmpty) { + ActionFailure(errors.mkString(";")) + } else { + ApexTestUtils.processCodeCoverage(runTestResult, session) match { + case Some(codeCoverageReport) => + ActionSuccess(com.neowit.response.LoadApexCodeCoverageAggregateResult(codeCoverageReport)) + case None => + ActionFailure("No coverage available for " + classOrTriggerName) + } + } + case None => + ActionFailure("Missing required parameter: classOrTriggerName") + } + Future.successful(actionResult) + } +} diff --git a/src/main/scala/com/neowit/apex/actions/tooling/RunTestConversions.scala b/src/main/scala/com/neowit/apex/actions/tooling/RunTestConversions.scala index c8a92d38..fcc4b934 100644 --- a/src/main/scala/com/neowit/apex/actions/tooling/RunTestConversions.scala +++ b/src/main/scala/com/neowit/apex/actions/tooling/RunTestConversions.scala @@ -42,6 +42,22 @@ object RunTestConversions { result } + def toCodeCoverageResult(coverageAggregate: ApexCodeCoverageAggregate): com.sforce.soap.tooling.CodeCoverageResult = { + val res = new com.sforce.soap.tooling.CodeCoverageResult() + res.setId(coverageAggregate.ApexClassOrTriggerId) + res.setName(coverageAggregate.ApexClassOrTrigger.Name) + res.setNumLocations(coverageAggregate.NumLinesCovered + coverageAggregate.NumLinesUncovered ) + res.setNumLocationsNotCovered(coverageAggregate.NumLinesUncovered) + res.setLocationsNotCovered( + coverageAggregate.Coverage.uncoveredLines.map{line => + val loc = new com.sforce.soap.tooling.CodeLocation() + loc.setLine(line) + loc + } + ) + res + } + private def toCodeCoverageResult(coverage: List[TestCodeCoverage]): Array[com.sforce.soap.tooling.CodeCoverageResult] = { coverage.map{c => val res = new com.sforce.soap.tooling.CodeCoverageResult() @@ -49,13 +65,17 @@ object RunTestConversions { //c.dmlInfo c.id.foreach(res.setId(_)) val locationsNotCovered = - c.locationsNotCovered.map{loc => - val res = new com.sforce.soap.tooling.CodeLocation() - loc.column.foreach(res.setColumn(_)) - loc.line.foreach(res.setLine(_)) - loc.numExecutions.foreach(res.setNumExecutions(_)) - loc.time.foreach(res.setTime(_)) - res + c.locationsNotCovered match { + case Some(locations) => + locations.map{loc => + val res = new com.sforce.soap.tooling.CodeLocation() + loc.column.foreach(res.setColumn(_)) + loc.line.foreach(res.setLine(_)) + loc.numExecutions.foreach(res.setNumExecutions(_)) + loc.time.foreach(res.setTime(_)) + res + } + case None => Nil } res.setLocationsNotCovered(locationsNotCovered.toArray) diff --git a/src/main/scala/com/neowit/apex/actions/tooling/RunTestJsonSupport.scala b/src/main/scala/com/neowit/apex/actions/tooling/RunTestJsonSupport.scala index 9f2ac48b..1208a648 100644 --- a/src/main/scala/com/neowit/apex/actions/tooling/RunTestJsonSupport.scala +++ b/src/main/scala/com/neowit/apex/actions/tooling/RunTestJsonSupport.scala @@ -37,6 +37,10 @@ trait RunTestJsonSupport extends JsonSupport { implicit val TestFailureFormat = jsonFormat10(TestFailure) implicit val RunTestsSynchronousResultFormat = jsonFormat8(RunTestsSynchronousResult) + + implicit val ApexClassOrTriggerFormat = jsonFormat1(ApexClassOrTrigger) + implicit val CoverageFormat = jsonFormat2(Coverage) + implicit val ApexCodeCoverageAggregateFormat = jsonFormat5(ApexCodeCoverageAggregate) } object RunTestJsonSupport { @@ -89,4 +93,13 @@ object RunTestJsonSupport { case class CodeLocation(line: Option[Int], column: Option[Int], numExecutions: Option[Int], time: Option[Double]) + case class ApexClassOrTrigger(Name: String) + case class Coverage(coveredLines: Array[Int], uncoveredLines: Array[Int]) + + case class ApexCodeCoverageAggregate(ApexClassOrTrigger: ApexClassOrTrigger, + ApexClassOrTriggerId: String, + Coverage: Coverage, + NumLinesCovered: Int, + NumLinesUncovered: Int) + } \ No newline at end of file diff --git a/src/main/scala/com/neowit/response/BaseResult.scala b/src/main/scala/com/neowit/response/BaseResult.scala index e19afe2f..bf051c47 100644 --- a/src/main/scala/com/neowit/response/BaseResult.scala +++ b/src/main/scala/com/neowit/response/BaseResult.scala @@ -68,4 +68,5 @@ case class ListModifiedResult(modified: List[File], deleted: List[File]) extends case class LoginOauthResult(tokens: Option[Oauth2Tokens], resultFileOpt: Option[File]) extends BaseResult case class RefreshMetadataResult(retrieveResult: Option[UpdateFromRetrieveResult], modifiedFiles: List[File]) extends BaseResult case class SoqlQueryResult(queryReport: SoqlQueryReport) extends BaseResult +case class LoadApexCodeCoverageAggregateResult(coverageReport: CodeCoverageReport) extends BaseResult diff --git a/src/main/scala/com/neowit/response/protocols/vim/ResponseWriterVim.scala b/src/main/scala/com/neowit/response/protocols/vim/ResponseWriterVim.scala index d2e734d4..19b2758f 100644 --- a/src/main/scala/com/neowit/response/protocols/vim/ResponseWriterVim.scala +++ b/src/main/scala/com/neowit/response/protocols/vim/ResponseWriterVim.scala @@ -128,6 +128,7 @@ class ResponseWriterVim(out: OutputStream, autoFlush: Boolean = true, append: Bo case res @ RefreshMetadataResult(_, _) => new RefreshMetadata(this).send(res) case res @ RunTestsResult(_, _, _, _, _) => new RunTests(this).send(res) case res @ SoqlQueryResult(_) => new SoqlQuery(this).send(res) + case res @ LoadApexCodeCoverageAggregateResult(_) => new RunTests(this).send(res) } } diff --git a/src/main/scala/com/neowit/response/protocols/vim/RunTests.scala b/src/main/scala/com/neowit/response/protocols/vim/RunTests.scala index 0b725aa8..f8e0e0ee 100644 --- a/src/main/scala/com/neowit/response/protocols/vim/RunTests.scala +++ b/src/main/scala/com/neowit/response/protocols/vim/RunTests.scala @@ -140,4 +140,14 @@ class RunTests(writer: ResponseWriterVim) extends VimProtocol[RunTestsResult] { } Unit } + def send(result: LoadApexCodeCoverageAggregateResult): Unit = { + printCoverageReport(writer, Option(result.coverageReport)) + + prepareDetailedPerFileCoverage(Option(result.coverageReport)) match { + case Some(resultFile) => + writer.send("COVERAGE_FILE=" + resultFile.getAbsolutePath) + case None => + } + Unit + } }