All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scoverage.Invoker.scala Maven / Gradle / Ivy

The newest version!
package scoverage

import scala.collection.Set
import scala.collection.mutable

import scoverage.Platform._

/** @author Stephen Samuel */
object Invoker {

  private val runtimeUUID = Platform.insecureRandomUUID()

  private val MeasurementsPrefix = "scoverage.measurements."
  private val threadFiles = new ThreadLocal[mutable.HashMap[String, FileWriter]]

  // For each data directory we maintain a thread-safe set tracking the ids that we've already
  // seen and recorded. We're using a map as a set, so we only care about its keys and can ignore
  // its values.
  private val dataDirToIds =
    ThreadSafeMap.empty[String, ThreadSafeMap[Int, Any]]

  /** We record that the given id has been invoked by appending its id to the coverage
    * data file.
    *
    * This will happen concurrently on as many threads as the application is using,
    * so we use one file per thread, named for the thread id.
    *
    * This method is not thread-safe if the threads are in different JVMs, because
    * the thread IDs may collide.
    * You may not use `scoverage` on multiple processes in parallel without risking
    * corruption of the measurement file.
    *
    * @param id the id of the statement that was invoked
    * @param dataDir the directory where the measurement data is held
    */
  def invoked(
      id: Int,
      dataDir: String,
      reportTestName: Boolean = false
  ): Unit = {
    // [sam] we can do this simple check to save writing out to a file.
    // This won't work across JVMs but since there's no harm in writing out the same id multiple
    // times since for coverage we only care about 1 or more, (it just slows things down to
    // do it more than once), anything we can do to help is good. This helps especially with code
    // that is executed many times quickly, eg tight loops.
    if (!dataDirToIds.contains(dataDir)) {
      // Guard against SI-7943: "TrieMap method getOrElseUpdate is not thread-safe".
      dataDirToIds.synchronized {
        if (!dataDirToIds.contains(dataDir)) {
          dataDirToIds(dataDir) = ThreadSafeMap.empty[Int, Any]
        }
      }
    }
    val ids = dataDirToIds(dataDir)
    if (!ids.contains(id)) {
      // Each thread writes to a separate measurement file, to reduce contention
      // and because file appends via FileWriter are not atomic on Windows.
      var files = threadFiles.get()
      if (files == null) {
        files = mutable.HashMap.empty[String, FileWriter]
        threadFiles.set(files)
      }
      val writer = files.getOrElseUpdate(
        dataDir,
        new FileWriter(measurementFile(dataDir), true)
      )

      if (reportTestName)
        writer
          .append(Integer.toString(id))
          .append(" ")
          .append(getCallingScalaTest)
          .append("\n")
          .flush()
      else writer.append(Integer.toString(id)).append("\n").flush()
      ids.put(id, ())
    }
  }

  def getCallingScalaTest: String =
    Thread.currentThread.getStackTrace
      .map(_.getClassName.toLowerCase)
      .find(name =>
        name.endsWith("suite") || name.endsWith("spec") || name.endsWith("test")
      )
      .getOrElse("")

  def measurementFile(dataDir: File): File = measurementFile(
    dataDir.getAbsolutePath()
  )
  def measurementFile(dataDir: String): File = new File(
    dataDir,
    MeasurementsPrefix + runtimeUUID + "." + Thread.currentThread.getId
  )

  def findMeasurementFiles(dataDir: String): Array[File] = findMeasurementFiles(
    new File(dataDir)
  )
  def findMeasurementFiles(dataDir: File): Array[File] =
    dataDir.listFiles(new FileFilter {
      override def accept(pathname: File): Boolean =
        pathname.getName().startsWith(MeasurementsPrefix)
    })

  // loads all the invoked statement ids from the given files
  def invoked(files: Seq[File]): Set[Int] = {
    val acc = mutable.Set[Int]()
    files.foreach { file =>
      val reader = Source.fromFile(file)
      for (line <- reader.getLines()) {
        if (!line.isEmpty) {
          acc += line.toInt
        }
      }
      reader.close()
    }
    acc
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy