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

scala.tools.nsc.interactive.tests.InteractiveTest.scala Maven / Gradle / Ivy

There is a newer version: 2.10.2_1
Show newest version
/* NSC -- new Scala compiler
 * Copyright 2009-2011 Scala Solutions and LAMP/EPFL
 * @author Martin Odersky
 */
package scala.tools.nsc.interactive
package tests

import scala.tools.nsc.Settings
import scala.tools.nsc.reporters.StoreReporter
import scala.tools.nsc.util.{BatchSourceFile, SourceFile, Position}
import scala.tools.nsc.io._

import scala.collection.{immutable, mutable}

/** A base class for writing interactive compiler tests.
 *
 *  This class tries to cover common functionality needed when testing the presentation
 *  compiler: instantiation source files, reloading, creating positions, instantiating
 *  the presentation compiler, random stress testing.
 *
 *  By default, this class loads all classes found under `src/`. They are found in
 *  `sourceFiles`. Positions can be created using `pos(file, line, col)`. The presentation
 *  compiler is available through `compiler`.
 *
 *  It is easy to test member completion and type at a given position. Source
 *  files are searched for /markers/. By default, the completion marker is `/*!*/` and the
 *  typedAt marker is `/*?*/`. Place these markers in your source files, and call `completionTests`
 *  and `typedAtTests` to print the results at all these positions. Sources are reloaded by `reloadSources`
 *  (blocking call). All ask operations are placed on the work queue without waiting for each one to
 *  complete before asking the next. After all asks, it waits for each response in turn and prints the result.
 *  The default timout is 5 seconds per operation.
 *
 *  The same mechanism can be used for custom operations. Use `askAllSources(marker)(f)(g)`. Give your custom
 *  marker, and provide the two functions: one for creating the request, and the second for processing the
 *  response, if it didn't time out and there was no error.
 *
 *  @see   Check existing tests under test/files/presentation
 *
 * @author Iulian Dragos
 */
abstract class InteractiveTest {

  val completionMarker = "/*!*/"
  val typedAtMarker = "/*?*/"
  val TIMEOUT = 10000 // timeout in milliseconds

  val settings = new Settings
  val reporter= new StoreReporter

  /** The root directory for this test suite, usually the test kind ("test/files/presentation"). */
  val outDir = Path(Option(System.getProperty("partest.cwd")).getOrElse("."))

  /** The base directory for this test, usually a subdirectory of "test/files/presentation/" */
  val baseDir = Option(System.getProperty("partest.testname")).map(outDir / _).getOrElse(Path("."))

  /** If there's a file ending in .opts, read it and parse it for cmd line arguments. */
  val argsString = {
    val optsFile = outDir / "%s.opts".format(System.getProperty("partest.testname"))
    val str = try File(optsFile).slurp() catch {
      case e: java.io.IOException => ""
    }
    str.lines.filter(!_.startsWith("#")).mkString(" ")
  }

  /** Prepare the settings object. Load the .opts file and adjust all paths from the
   *  Unix-like syntax to the platform specific syntax. This is necessary so that a
   *  single .opts file can be used on all platforms.
   *
   *  @note Bootclasspath is treated specially. If there is a -bootclasspath option in
   *        the file, the 'usejavacp' setting is set to false. This ensures that the
   *        bootclasspath takes precedence over the scala-library used to run the current
   *        test.
   */
  def prepareSettings() {
    import java.io.File._
    def adjustPaths(paths: settings.PathSetting*) {
      for (p <- paths if argsString.contains(p.name)) p.value = p.value.map {
        case '/' => separatorChar
        case ':' => pathSeparatorChar
        case c => c
      }
    }

    // need this so that the classpath comes from what partest
    // instead of scala.home
    settings.usejavacp.value = !argsString.contains("-bootclasspath")

    // pass any options coming from outside
    settings.processArgumentString(argsString) match {
      case (false, rest) =>
        println("error processing arguments (unprocessed: %s)".format(rest))
      case _ => ()
    }
    adjustPaths(settings.bootclasspath, settings.classpath, settings.javabootclasspath, settings.sourcepath)
  }

  protected def printClassPath() {
    println("\toutDir: %s".format(outDir.path))
    println("\tbaseDir: %s".format(baseDir.path))
    println("\targsString: %s".format(argsString))
    println("\tbootClassPath: %s".format(settings.bootclasspath.value))
    println("\tverbose: %b".format(settings.verbose.value))
  }

  lazy val compiler = {
    prepareSettings()
    new Global(settings, reporter)
  }

  def sources(filename: String*): Seq[SourceFile] =
    for (f <- filename) yield
      source(if (f.startsWith("/")) Path(f) else baseDir / f)

  def source(file: Path) = new BatchSourceFile(AbstractFile.getFile(file.toFile))
  def source(filename: String): SourceFile = new BatchSourceFile(AbstractFile.getFile(filename))

  def pos(file: SourceFile, line: Int, col: Int): Position =
    file.position(line, col)

  def filesInDir(dir: Path): Iterator[Path]  = {
    dir.toDirectory.list.filter(_.isFile)
  }

  /** Where source files are placed. */
  val sourceDir = "src"

  /** All .scala files below "src" directory. */
  lazy val sourceFiles: Array[SourceFile] =
    filesInDir(baseDir / sourceDir).filter(_.extension == "scala").map(source).toArray

  /** All positions of the given string in all source files. */
  def allPositionsOf(sources: Seq[SourceFile] = sourceFiles, str: String): immutable.Map[SourceFile, Seq[Position]] = {
    (for (s <- sources; p <- positionsOf(s, str)) yield p).groupBy(_.source)
  }

  /** Return all positions of the given str in the given source file. */
  def positionsOf(source: SourceFile, str: String): Seq[Position] = {
    val buf = new mutable.ListBuffer[Position]
    var pos = source.content.indexOfSlice(str)
    while (pos >= 0) {
//      buf += compiler.rangePos(source, pos - 1, pos - 1, pos - 1)
      buf += source.position(pos - 1) // we need the position before the first character of this marker
      pos = source.content.indexOfSlice(str, pos + 1)
    }
    buf.toList
  }

  /** Should askAllSources wait for each ask to finish before issueing the next? */
  val synchronousRequests = true

  /** Perform an operation on all sources at all positions that match the given
   *  marker string. For instance, askAllSources("/*!*/")(askTypeAt)(println) would
   *  ask the tyep at all positions marked with /*!*/ and println the result.
   */
  def askAllSourcesAsync[T](marker: String)(askAt: Position => Response[T])(f: (Position, T) => Unit) {
    val positions = allPositionsOf(str = marker).valuesIterator.toList.flatten
    val responses = for (pos <- positions) yield askAt(pos)

    for ((pos, r) <- positions zip responses) withResponse(pos, r)(f)
  }

  /** Synchronous version of askAllSources. Each position is treated in turn, waiting for the
   *  response before going to the next one.
   */
  def askAllSourcesSync[T](marker: String)(askAt: Position => Response[T])(f: (Position, T) => Unit) {
    val positions = allPositionsOf(str = marker).valuesIterator.toList.flatten
    for (pos <- positions) withResponse(pos, askAt(pos))(f)
  }

  def askAllSources[T] = if (synchronousRequests) askAllSourcesSync[T] _ else askAllSourcesAsync[T] _

  /** Return the filename:line:col version of this position. */
  def showPos(pos: Position): String =
    "%s:%d:%d".format(pos.source.file.name, pos.line, pos.column)

  protected def withResponse[T](pos: Position, response: Response[T])(f: (Position, T) => Unit) {
    response.get(TIMEOUT) match {
      case Some(Left(t)) =>
        f(pos, t)
      case None =>
        println("TIMEOUT: " + showPos(pos))
      case Some(r) =>
        println("ERROR: " + r)
    }
  }

  /** Ask completion for all marked positions in all sources.
   *  A completion position is marked with /*!*/.
   */
  def completionTests() {
    askAllSources(completionMarker) { pos =>
      println("askTypeCompletion at " + pos.source.file.name + ((pos.line, pos.column)))
      val r = new Response[List[compiler.Member]]
      compiler.askTypeCompletion(pos, r)
      r
    } { (pos, members) =>
      println("\n" + "=" * 80)
      println("[response] aksTypeCompletion at " + (pos.line, pos.column))
      // we skip getClass because it changed signature between 1.5 and 1.6, so there is no
      // universal check file that we can provide for this to work
      println("retreived %d members".format(members.size))
      compiler ask { () =>
        println(members.sortBy(_.sym.name.toString).filterNot(_.sym.name.toString == "getClass").mkString("\n"))
      }
    }
  }

  /** Ask for typedAt for all marker positions in all sources.
   */
  def typeAtTests() {
    askAllSources(typedAtMarker) { pos =>
      println("askTypeAt at " + pos.source.file.name + ((pos.line, pos.column)))
      val r = new Response[compiler.Tree]
      compiler.askTypeAt(pos, r)
      r
    } { (pos, tree) =>
      println("[response] askTypeAt at " + (pos.line, pos.column))
      compiler.ask(() => println(tree))
    }
  }

  /** Reload the given source files and wait for them to be reloaded. */
  def reloadSources(sources: Seq[SourceFile] = sourceFiles) {
//    println("basedir: " + baseDir.path)
//    println("sourcedir: " + (baseDir / sourceDir).path)
    println("reload: " + sourceFiles.mkString("", ", ", ""))
    val reload = new Response[Unit]
    compiler.askReload(sourceFiles.toList, reload)
    reload.get
  }

  def runTest(): Unit = {
    if (runRandomTests) randomTests(20, sourceFiles)
    completionTests()
    typeAtTests()
  }

  /** Perform n random tests with random changes. */
  def randomTests(n: Int, files: Array[SourceFile]) {
    val tester = new Tester(n, files, settings)
    tester.run()
  }

  val runRandomTests = false

  def main(args: Array[String]) {
    reloadSources()
    runTest
    compiler.askShutdown()
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy