Skip to content

Convert sourcepath to relative. #385

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 63 additions & 33 deletions scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,51 +11,74 @@ import scala.io.Source

object Serializer {

val coverageDataFormatVersion = "3.0"

// Write out coverage data to the given data directory, using the default coverage filename
def serialize(coverage: Coverage, dataDir: String): Unit =
serialize(coverage, coverageFile(dataDir))
def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit =
serialize(coverage, coverageFile(dataDir), new File(sourceRoot))

// Write out coverage data to given file.
def serialize(coverage: Coverage, file: File): Unit = {
def serialize(coverage: Coverage, file: File, sourceRoot: File): Unit = {
val writer: Writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), Codec.UTF8.name)
)
try {
serialize(coverage, writer)
serialize(coverage, writer, sourceRoot)
} finally {
writer.flush()
writer.close()
}
}

def serialize(coverage: Coverage, writer: Writer): Unit = {
def serialize(
coverage: Coverage,
writer: Writer,
sourceRoot: File
): Unit = {
def getRelativePath(filePath: String): String = {
val base = sourceRoot.getCanonicalFile().toPath()
// NOTE: In the real world I have no idea if it's likely that you'll end
// up with weird issues on windows where the roots don't match, something
// like your root being D:/ and your file being C:/. If so this blows up.
// This happened on windows CI for me, since I was using a temp dir, and
// then trying to reletavize it off the cwd, which were in different
// drives. For now, we'll let this as is, but if 'other' has different
// root ever shows its, we'll shut that down real quick here... just not
// sure what to do in that situation yet.
val relPath =
base.relativize(new File(filePath).getCanonicalFile().toPath())
relPath.toString
}

def writeHeader(writer: Writer): Unit = {
writer.write(s"""# Coverage data, format version: 2.0
|# Statement data:
|# - id
|# - source path
|# - package name
|# - class name
|# - class type (Class, Object or Trait)
|# - full class name
|# - method name
|# - start offset
|# - end offset
|# - line number
|# - symbol name
|# - tree name
|# - is branch
|# - invocations count
|# - is ignored
|# - description (can be multi-line)
|# '\f' sign
|# ------------------------------------------
|""".stripMargin.replaceAll("(\r\n)|\n|\r", "\n"))
writer.write(
s"""# Coverage data, format version: $coverageDataFormatVersion
|# Statement data:
|# - id
|# - source path
|# - package name
|# - class name
|# - class type (Class, Object or Trait)
|# - full class name
|# - method name
|# - start offset
|# - end offset
|# - line number
|# - symbol name
|# - tree name
|# - is branch
|# - invocations count
|# - is ignored
|# - description (can be multi-line)
|# '\f' sign
|# ------------------------------------------
|""".stripMargin
)
}

def writeStatement(stmt: Statement, writer: Writer): Unit = {
writer.write(s"""${stmt.id}
|${stmt.location.sourcePath}
|${getRelativePath(stmt.location.sourcePath)}
|${stmt.location.packageName}
|${stmt.location.className}
|${stmt.location.classType}
Expand All @@ -71,7 +94,7 @@ object Serializer {
|${stmt.ignored}
|${stmt.desc}
|\f
|""".stripMargin.replaceAll("(\r\n)|\n|\r", "\n"))
|""".stripMargin)
}

writeHeader(writer)
Expand All @@ -84,13 +107,20 @@ object Serializer {
def coverageFile(dataDir: String): File =
new File(dataDir, Constants.CoverageFileName)

def deserialize(file: File): Coverage = {
def deserialize(file: File, sourceRoot: File): Coverage = {
val source = Source.fromFile(file)(Codec.UTF8)
try deserialize(source.getLines())
try deserialize(source.getLines(), sourceRoot)
finally source.close()
}

def deserialize(lines: Iterator[String]): Coverage = {
def deserialize(lines: Iterator[String], sourceRoot: File): Coverage = {
// To integrate it smoothly with rest of the report writers,
// it is necessary to again convert [sourcePath] into a
// canonical one.
def getAbsolutePath(filePath: String): String = {
new File(sourceRoot, filePath).getCanonicalPath()
}

def toStatement(lines: Iterator[String]): Statement = {
val id: Int = lines.next().toInt
val sourcePath = lines.next()
Expand All @@ -105,7 +135,7 @@ object Serializer {
fullClassName,
ClassType.fromString(classType),
method,
sourcePath
getAbsolutePath(sourcePath)
)
val start: Int = lines.next().toInt
val end: Int = lines.next().toInt
Expand Down Expand Up @@ -133,7 +163,7 @@ object Serializer {

val headerFirstLine = lines.next()
require(
headerFirstLine == "# Coverage data, format version: 2.0",
headerFirstLine == s"# Coverage data, format version: $coverageDataFormatVersion",
"Wrong file format"
)

Expand Down
22 changes: 21 additions & 1 deletion scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class ScoveragePlugin(val global: Global) extends Plugin {
options.excludedSymbols = parseExclusionEntry("excludedSymbols:", opt)
} else if (opt.startsWith("dataDir:")) {
options.dataDir = opt.substring("dataDir:".length)
} else if (opt.startsWith("sourceRoot:")) {
options.sourceRoot = opt.substring("sourceRoot:".length())
} else if (
opt
.startsWith("extraAfterPhase:") || opt.startsWith("extraBeforePhase:")
Expand All @@ -66,13 +68,18 @@ class ScoveragePlugin(val global: Global) extends Plugin {
throw new RuntimeException(
"Cannot invoke plugin without specifying <dataDir>"
)
if (!opts.exists(_.startsWith("sourceRoot:")))
throw new RuntimeException(
"Cannot invoke plugin without specifying <sourceRoot>"
)
instrumentationComponent.setOptions(options)
true
}

override val optionsHelp: Option[String] = Some(
Seq(
"-P:scoverage:dataDir:<pathtodatadir> where the coverage files should be written\n",
"-P:scoverage:sourceRoot:<pathtosourceRoot> the root dir of your sources, used for path relativization\n",
"-P:scoverage:excludedPackages:<regex>;<regex> semicolon separated list of regexs for packages to exclude",
"-P:scoverage:excludedFiles:<regex>;<regex> semicolon separated list of regexs for paths to exclude",
"-P:scoverage:excludedSymbols:<regex>;<regex> semicolon separated list of regexs for symbols to exclude",
Expand Down Expand Up @@ -107,6 +114,8 @@ class ScoveragePlugin(val global: Global) extends Plugin {
}
}

// TODO refactor this into a case class. We'll also refactor how we parse the
// options to get rid of all these vars
class ScoverageOptions {
var excludedPackages: Seq[String] = Nil
var excludedFiles: Seq[String] = Nil
Expand All @@ -117,6 +126,12 @@ class ScoverageOptions {
)
var dataDir: String = IOUtils.getTempPath
var reportTestName: Boolean = false
// TODO again, we'll refactor this later so this won't have a default here.
// However for tests we'll have to create this. However, make sure you create
// either both in temp or neither in temp, since on windows your temp dir
// will be in another drive, so the relativize functinality won't work if
// correctly.
var sourceRoot: String = IOUtils.getTempPath
}

class ScoverageInstrumentationComponent(
Expand Down Expand Up @@ -179,7 +194,12 @@ class ScoverageInstrumentationComponent(
s"Instrumentation completed [${coverage.statements.size} statements]"
)

Serializer.serialize(coverage, Serializer.coverageFile(options.dataDir))
// TODO do we need to verify this sourceRoot exists? How does semanticdb do this?
Serializer.serialize(
coverage,
Serializer.coverageFile(options.dataDir),
new File(options.sourceRoot)
)
reporter.echo(
s"Wrote instrumentation file [${Serializer.coverageFile(options.dataDir)}]"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,32 @@ import scoverage.Serializer

object CoverageAggregator {

@deprecated("1.4.0", "Used only by gradle-scoverage plugin")
def aggregate(baseDir: File, clean: Boolean): Option[Coverage] = {
aggregate(IOUtils.scoverageDataDirsSearch(baseDir))
}

// to be used by gradle-scoverage plugin
def aggregate(dataDirs: Array[File]): Option[Coverage] = aggregate(
dataDirs.toSeq
)
def aggregate(dataDirs: Array[File], sourceRoot: File): Option[Coverage] =
aggregate(
dataDirs.toSeq,
sourceRoot
)

def aggregate(dataDirs: Seq[File]): Option[Coverage] = {
def aggregate(dataDirs: Seq[File], sourceRoot: File): Option[Coverage] = {
println(
s"[info] Found ${dataDirs.size} subproject scoverage data directories [${dataDirs.mkString(",")}]"
)
if (dataDirs.size > 0) {
Some(aggregatedCoverage(dataDirs))
Some(aggregatedCoverage(dataDirs, sourceRoot))
} else {
None
}
}

def aggregatedCoverage(dataDirs: Seq[File]): Coverage = {
def aggregatedCoverage(dataDirs: Seq[File], sourceRoot: File): Coverage = {
var id = 0
val coverage = Coverage()
dataDirs foreach { dataDir =>
val coverageFile: File = Serializer.coverageFile(dataDir)
if (coverageFile.exists) {
val subcoverage: Coverage = Serializer.deserialize(coverageFile)
val subcoverage: Coverage =
Serializer.deserialize(coverageFile, sourceRoot)
val measurementFiles: Array[File] =
IOUtils.findMeasurementFiles(dataDir)
val measurements = IOUtils.invoked(measurementFiles.toIndexedSeq)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import scoverage.report.CoverageAggregator
class CoverageAggregatorTest extends AnyFreeSpec with Matchers {

// Let current directory be our source root
private val sourceRoot = new File(".")
private val sourceRoot = new File(".").getCanonicalPath()
private def canonicalPath(fileName: String) =
new File(sourceRoot, fileName).getCanonicalPath
new File(sourceRoot, fileName).getCanonicalPath()

"coverage aggregator" - {
"should merge coverage objects with same id" in {
Expand All @@ -35,7 +35,11 @@ class CoverageAggregatorTest extends AnyFreeSpec with Matchers {
coverage1.add(cov1Stmt2.copy(count = 0))
val dir1 = new File(IOUtils.getTempPath, UUID.randomUUID.toString)
dir1.mkdir()
Serializer.serialize(coverage1, Serializer.coverageFile(dir1))
Serializer.serialize(
coverage1,
Serializer.coverageFile(dir1),
new File(sourceRoot)
)
val measurementsFile1 =
new File(dir1, s"${Constants.MeasurementsPrefix}1")
val measurementsFile1Writer = new FileWriter(measurementsFile1)
Expand All @@ -47,23 +51,34 @@ class CoverageAggregatorTest extends AnyFreeSpec with Matchers {
coverage2.add(cov2Stmt1)
val dir2 = new File(IOUtils.getTempPath, UUID.randomUUID.toString)
dir2.mkdir()
Serializer.serialize(coverage2, Serializer.coverageFile(dir2))
Serializer.serialize(
coverage2,
Serializer.coverageFile(dir2),
new File(sourceRoot)
)

val cov3Stmt1 =
Statement(location, 2, 14, 1515, 544, "", "", "", false, 1)
val coverage3 = Coverage()
coverage3.add(cov3Stmt1.copy(count = 0))
val dir3 = new File(IOUtils.getTempPath, UUID.randomUUID.toString)
dir3.mkdir()
Serializer.serialize(coverage3, Serializer.coverageFile(dir3))
Serializer.serialize(
coverage3,
Serializer.coverageFile(dir3),
new File(sourceRoot)
)
val measurementsFile3 =
new File(dir3, s"${Constants.MeasurementsPrefix}1")
val measurementsFile3Writer = new FileWriter(measurementsFile3)
measurementsFile3Writer.write("2\n")
measurementsFile3Writer.close()

val aggregated =
CoverageAggregator.aggregatedCoverage(Seq(dir1, dir2, dir3))
CoverageAggregator.aggregatedCoverage(
Seq(dir1, dir2, dir3),
new File(sourceRoot)
)
aggregated.statements.toSet.size shouldBe 4
aggregated.statements.map(_.copy(id = 0)).toSet shouldBe
Set(cov1Stmt1, cov1Stmt2, cov2Stmt1, cov3Stmt1).map(_.copy(id = 0))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class ScoverageCompiler(

val instrumentationComponent =
new ScoverageInstrumentationComponent(this, None, None)

instrumentationComponent.setOptions(new ScoverageOptions())
val testStore = new ScoverageTestStoreComponent(this)
val validator = new PositionValidator(this)
Expand Down
Loading