summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/interactive/tests/InteractiveTest.scala
blob: 1cda29fdbae36a7014f7312071cbca5c3f5fb88f (plain) (blame)
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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

  // need this so that the classpath comes from what partest
  // instead of scala.home
  settings.usejavacp.value = true

  /** 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("."))

//  settings.YpresentationDebug.value = true
  lazy val compiler = 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
  }

  /** 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 askAllSources[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) r.get(TIMEOUT) match {
      case Some(Left(members)) =>
        f(pos, members)
      case None =>
        println("TIMEOUT: " + r)
      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(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))
      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()
  }
}