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

scoverage.coverage.scala Maven / Gradle / Ivy

The newest version!
package scoverage

import java.io.File

import scoverage.DoubleFormat.twoFractionDigits

import scala.collection.mutable

/**
 * @author Stephen Samuel */
case class Coverage()
  extends CoverageMetrics
  with MethodBuilders
  with java.io.Serializable
  with ClassBuilders
  with PackageBuilders
  with FileBuilders {

  private val statementsById = mutable.Map[Int, Statement]()
  override def statements = statementsById.values
  def add(stmt: Statement): Unit = statementsById.put(stmt.id, stmt)

  private val ignoredStatementsById = mutable.Map[Int, Statement]()
  override def ignoredStatements = ignoredStatementsById.values
  def addIgnoredStatement(stmt: Statement): Unit = ignoredStatementsById.put(stmt.id, stmt)


  def avgClassesPerPackage = classCount / packageCount.toDouble
  def avgClassesPerPackageFormatted: String = twoFractionDigits(avgClassesPerPackage)

  def avgMethodsPerClass = methodCount / classCount.toDouble
  def avgMethodsPerClassFormatted: String = twoFractionDigits(avgMethodsPerClass)

  def loc = files.map(_.loc).sum
  def linesPerFile = loc / fileCount.toDouble
  def linesPerFileFormatted: String = twoFractionDigits(linesPerFile)

  // returns the classes by least coverage
  def risks(limit: Int) = classes.toSeq.sortBy(_.statementCount).reverse.sortBy(_.statementCoverage).take(limit)

  def apply(ids: Iterable[Int]): Unit = ids foreach invoked
  def invoked(id: Int): Unit = statementsById.get(id).foreach(_.invoked())
}

trait MethodBuilders {
  def statements: Iterable[Statement]
  def methods: Seq[MeasuredMethod] = {
    statements.groupBy(stmt => stmt.location.packageName + "/" + stmt.location.className + "/" + stmt.location.method)
      .map(arg => MeasuredMethod(arg._1, arg._2))
      .toSeq
  }
  def methodCount = methods.size
}

trait PackageBuilders {
  def statements: Iterable[Statement]
  def packageCount = packages.size
  def packages: Seq[MeasuredPackage] = {
    statements.groupBy(_.location.packageName).map(arg => MeasuredPackage(arg._1, arg._2)).toSeq.sortBy(_.name)
  }
}

trait ClassBuilders {
  def statements: Iterable[Statement]
  def classes = statements.groupBy(_.location.fullClassName).map(arg => MeasuredClass(arg._1, arg._2))
  def classCount: Int = classes.size
}

trait FileBuilders {
  def statements: Iterable[Statement]
  def files: Iterable[MeasuredFile] = statements.groupBy(_.source).map(arg => MeasuredFile(arg._1, arg._2))
  def fileCount: Int = files.size
}

case class MeasuredMethod(name: String, statements: Iterable[Statement]) extends CoverageMetrics {
  override def ignoredStatements: Iterable[Statement] = Seq()
}

case class MeasuredClass(fullClassName: String, statements: Iterable[Statement])
  extends CoverageMetrics with MethodBuilders {

  def source: String = statements.head.source
  def loc = statements.map(_.line).max

  /**
   * The class name for display is the FQN minus the package,
   * for example "com.a.Foo.Bar.Baz" should display as "Foo.Bar.Baz"
   * and "com.a.Foo" should display as "Foo".
   *
   * This is used in the class lists in the package and overview pages.
   */
  def displayClassName = statements.headOption.map(_.location).map { location =>
    location.fullClassName.stripPrefix(location.packageName + ".")
  }.getOrElse(fullClassName)

  override def ignoredStatements: Iterable[Statement] = Seq()
}

case class MeasuredPackage(name: String, statements: Iterable[Statement])
  extends CoverageMetrics with ClassCoverage with ClassBuilders with FileBuilders {
  override def ignoredStatements: Iterable[Statement] = Seq()
}

case class MeasuredFile(source: String, statements: Iterable[Statement])
  extends CoverageMetrics with ClassCoverage with ClassBuilders {
  def filename = new File(source).getName
  def loc = statements.map(_.line).max

  override def ignoredStatements: Iterable[Statement] = Seq()
}

case class Statement(location: Location,
                     id: Int,
                     start: Int,
                     end: Int,
                     line: Int,
                     desc: String,
                     symbolName: String,
                     treeName: String,
                     branch: Boolean,
                     var count: Int = 0,
                     ignored: Boolean = false) extends java.io.Serializable {
  def source = location.sourcePath
  def invoked(): Unit = count = count + 1
  def isInvoked = count > 0
}

sealed trait ClassType
object ClassType {
  case object Object extends ClassType
  case object Class extends ClassType
  case object Trait extends ClassType
  def fromString(str: String): ClassType = {
    str.toLowerCase match {
      case "object" => Object
      case "trait" => Trait
      case _ => Class
    }
  }
}

case class ClassRef(name: String) {
  lazy val simpleName = name.split(".").last
  lazy val getPackage = name.split(".").dropRight(1).mkString(".")
}

object ClassRef {
  def fromFilepath(path: String) = ClassRef(path.replace('/', '.'))
  def apply(_package: String, className: String): ClassRef = ClassRef(_package.replace('/', '.') + "." + className)
}

trait CoverageMetrics {
  def statements: Iterable[Statement]
  def statementCount: Int = statements.size

  def ignoredStatements: Iterable[Statement]
  def ignoredStatementCount: Int = ignoredStatements.size

  def invokedStatements: Iterable[Statement] = statements.filter(_.count > 0)
  def invokedStatementCount = invokedStatements.size
  def statementCoverage: Double = if (statementCount == 0) 1 else invokedStatementCount / statementCount.toDouble
  def statementCoveragePercent = statementCoverage * 100
  def statementCoverageFormatted: String = twoFractionDigits(statementCoveragePercent)
  def branches: Iterable[Statement] = statements.filter(_.branch)
  def branchCount: Int = branches.size
  def branchCoveragePercent = branchCoverage * 100
  def invokedBranches: Iterable[Statement] = branches.filter(_.count > 0)
  def invokedBranchesCount = invokedBranches.size

  /**
   * @see http://stackoverflow.com/questions/25184716/scoverage-ambiguous-measurement-from-branch-coverage
   */
  def branchCoverage: Double = {
    // if there are zero branches, then we have a single line of execution.
    // in that case, if there is at least some coverage, we have covered the branch.
    // if there is no coverage then we have not covered the branch
    if (branchCount == 0) {
      if (statementCoverage > 0) 1
      else 0
    } else {
      invokedBranchesCount / branchCount.toDouble
    }
  }
  def branchCoverageFormatted: String = twoFractionDigits(branchCoveragePercent)
}

trait ClassCoverage {
  this: ClassBuilders =>
  val statements: Iterable[Statement]
  def invokedClasses: Int = classes.count(_.statements.count(_.count > 0) > 0)
  def classCoverage: Double = invokedClasses / classes.size.toDouble
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy