Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 90 lines (75 sloc) 3.599 kb
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
package scala.tools.eclipse.launching

import scala.collection.mutable.ListBuffer
import scala.tools.eclipse.ScalaPresentationCompiler
import scala.tools.eclipse.javaelements.ScalaSourceFile
import scala.tools.eclipse.logging.HasLogger
import scala.tools.nsc.MissingRequirementError
import org.eclipse.jdt.core.IType

/** Given the Scala AST of any compilation unit, traverse the AST and collect all top level class
* definitions that can be executed by the JUnit4 runtime.
*
* Note: This implementation assumes that the passed tree corresponds to the whole compilation unit.
* The reason is that it looks for top level class declarations to determine if there are
* any "runnable" JUnit test classes. This assumption is of course enforced in the code,
* have a look at the companion object.
*/
private[launching] abstract class JUnit4TestClassesCollector {
  protected val global: ScalaPresentationCompiler

  import global._

  /** Collect all top level class definitions that can be executed by the JUnit4 runtime.
*
* @param tree The compilation unit's Scala AST.
*/
  def collect(tree: Tree): List[ClassDef] = {
    val collector = new JUnit4TestClassesTraverser
    collector.traverse(tree)
    collector.hits.toList
  }

  /** Traverse the passed `Tree` and collect all class definitions that can be executed by the JUnit4 runtime. */
  private class JUnit4TestClassesTraverser extends global.Traverser {
    import JUnit4TestClassesTraverser._

    val hits: ListBuffer[ClassDef] = ListBuffer.empty

    override def traverse(tree: Tree): Unit = tree match {
      case _: PackageDef => super.traverse(tree)
      case cdef: ClassDef if isRunnableTestClass(cdef) => hits += cdef
      case _ => ()
    }

    private def isRunnableTestClass(cdef: ClassDef): Boolean = {
      global.askOption { () => isTopLevelClass(cdef) && isTestClass(cdef) } getOrElse false
    }

    private def isTopLevelClass(cdef: ClassDef): Boolean = {
      def isConcreteClass: Boolean = {
        val csym = cdef.symbol
        // Abstract classes cannot be run by definition, so they should be ignored.
        // And trait do have the `isClass` member set to `true`, so we need to check `isTrait` as well.
        csym.isClass && !csym.isAbstractClass && !csym.isTrait
      }
      isConcreteClass && cdef.symbol.owner.isPackageClass
    }

    private def isTestClass(cdef: ClassDef): Boolean = hasRunWithAnnotation(cdef) || hasJUnitTestMethod(cdef)

    private def hasRunWithAnnotation(cdef: ClassDef): Boolean = RunWithAnnotationOpt exists { runWithAnn =>
      cdef.symbol.info.baseClasses exists { hasAnnotation(_, runWithAnn) }
    }

    private def hasJUnitTestMethod(cdef: ClassDef): Boolean = TestAnnotationOpt exists { ta =>
      cdef.symbol.info.members.exists { hasAnnotation(_, ta) }
    }

    private def hasAnnotation(member: Symbol, ann: Symbol): Boolean = {
      if (!member.isInitialized) member.initialize
      member.hasAnnotation(ann)
    }
  }

  private object JUnit4TestClassesTraverser extends HasLogger {
    private lazy val TestAnnotationOpt = getClassSafe("org.junit.Test")
    private lazy val RunWithAnnotationOpt = getClassSafe("org.junit.runner.RunWith")

    /** Don't crash if the class is not on the classpath. */
    private def getClassSafe(fullName: String): Option[Symbol] = {
      try {
        Option(definitions.getClass(newTypeName(fullName)))
      } catch {
        case _: MissingRequirementError =>
          logger.info("Type `" + fullName + "` is not available in the project's classpath.")
          None
      }
    }
  }
}
Something went wrong with that request. Please try again.