summaryrefslogtreecommitdiff
path: root/project/Partest.scala
blob: fbb0a2a98035bae900c5bb0d6727be097a965bd3 (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
import sbt._

import Build._
import Keys._
import Project.Initialize
import complete._
import scala.collection.{ mutable, immutable }

/** This object */
object partest {

  /** The key for the run-partest task that exists in Scala's test suite. */
  lazy val runPartest       = TaskKey[Unit]("run-partest", "Runs the partest test suite against the quick.")
  lazy val runPartestSingle = InputKey[Unit]("run-partest-single", "Runs a single partest test against quick.")
  lazy val runPartestFailed = TaskKey[Unit]("run-partest-failed", "Runs failed partest tests.")
  lazy val runPartestGrep   = InputKey[Unit]("run-partest-grep", "Runs a single partest test against quick.")
  lazy val partestRunner    = TaskKey[PartestRunner]("partest-runner", "Creates a runner that can run partest suites")
  lazy val partestTests     = TaskKey[Map[String, Seq[File]]]("partest-tests", "Creates a map of test-type to a sequence of the test files/directoryies to test.")
  lazy val partestDirs      = SettingKey[Map[String,File]]("partest-dirs", "The map of partest test type to directory associated with that test type")

  lazy val partestTaskSettings: Seq[Setting[_]] = Seq(
    javaOptions in partestRunner := Seq("-Xmx512M -Xms256M"),
    partestDirs <<= baseDirectory apply { bd =>
      partestTestTypes map (kind => kind -> (bd / "test" / "files" / kind)) toMap
    },
    partestRunner <<= partestRunnerTask(fullClasspath in Runtime, javaOptions in partestRunner),
    partestTests <<= partestTestsTask(partestDirs),
    runPartest <<= runPartestTask(partestRunner, partestTests, scalacOptions in Test),
    runPartestSingle <<= runSingleTestTask(partestRunner, partestDirs, scalacOptions in Test),
    runPartestFailed <<= runPartestTask(partestRunner, partestTests, scalacOptions in Test, Seq("--failed"))
  )

  // What's fun here is that we want "*.scala" files *and* directories in the base directory...
  def partestResources(base: File, testType: String): PathFinder = testType match {
    case "res"          => base ** "*.res"
    case "buildmanager" => base * "*"
    // TODO - Only allow directories that have "*.scala" children...
    case _              => base * "*" filter { f => !f.getName.endsWith(".obj") && (f.isDirectory || f.getName.endsWith(".scala")) }
  }
  lazy val partestTestTypes = Seq("run", "jvm", "pos", "neg", "buildmanager", "res", "shootout", "scalap", "specialized", "presentation", "scalacheck")

  // TODO - Figure out how to specify only a subset of resources...
  def partestTestsTask(testDirs: ScopedSetting[Map[String,File]]): Project.Initialize[Task[Map[String, Seq[File]]]] =
    testDirs map (m => m map { case (kind, dir) => kind -> partestResources(dir, kind).get })

  // TODO - Split partest task into Configurations and build a Task for each Configuration.
  // *then* mix all of them together for run-testsuite or something clever like this.
  def runPartestTask(runner: ScopedTask[PartestRunner], testRuns: ScopedTask[Map[String,Seq[File]]], scalacOptions: ScopedTask[Seq[String]], extraArgs: Seq[String] = Seq()): Initialize[Task[Unit]] = {
    (runner, testRuns, scalacOptions, streams) map {
      (runner, runs, scalaOpts, s) => runPartestImpl(runner, runs, scalaOpts, s, extraArgs)
    }
  }
  private def runPartestImpl(runner: PartestRunner, runs: Map[String, Seq[File]], scalacOptions: Seq[String], s: TaskStreams, extras: Seq[String] = Seq()): Unit = {
    val testArgs  = runs.toSeq collect { case (kind, files) if files.nonEmpty => Seq("-" + kind, files mkString ",") } flatten
    val extraArgs = scalacOptions flatMap (opt => Seq("-scalacoption", opt))

    import collection.JavaConverters._
    val results = runner run Array(testArgs ++ extraArgs ++ extras: _*) asScala
    // TODO - save results
    val failures = results collect {
      case (path, "FAIL") => path + " [FAILED]"
      case (path, "TIMEOUT") => path + " [TIMEOUT]"
    }

    if (failures.isEmpty)
      s.log.info(""+results.size+" tests passed.")
    else {
      failures foreach (s.log error _)
      error("Test Failures! ("+failures.size+" of "+results.size+")")
    }
  }

  def convertTestsForAutoComplete(tests: Map[String, Seq[File]]): (Set[String], Set[String]) =
    (tests.keys.toSet, tests.values flatMap (_ map cleanFileName) toSet)

  /** Takes a test file, as sent ot Partest, and cleans it up for auto-complete */
  def cleanFileName(file: File): String = {
    // TODO - Something intelligent here
    val TestPattern = ".*/test/(.*)".r
    file.getCanonicalPath match {
      case TestPattern(n) => n
      case _ => file.getName
    }
  }

  // TODO - Allow a filter for the second part of this...
  def runSingleTestParser(testDirs: Map[String, File]): State => Parser[(String, String)] = {
    import DefaultParsers._
    state => {
      Space ~> token(NotSpace examples testDirs.keys.toSet) flatMap { kind =>
        val files: Set[String] = testDirs get kind match {
          case Some(dir) =>
            partestResources(dir, kind).get flatMap (_ relativeTo dir) map (_ getName) toSet
          case _ =>
            Set()
        }
        Space ~> token(NotSpace examples files) map (kind -> _)
      }
    }
  }

  def runSingleTestTask(runner: ScopedTask[PartestRunner], testDirs: ScopedSetting[Map[String, File]], scalacOptions: ScopedTask[Seq[String]]) : Initialize[InputTask[Unit]] = {
    import sbinary.DefaultProtocol._

    InputTask(testDirs apply runSingleTestParser) { result =>
      (runner, result, testDirs, scalacOptions, streams) map {
        case (r, (kind, filter), dirs, o, s) =>
        // TODO - Use partest resources somehow to filter the filter correctly....
        val files: Seq[File] =
          if (filter == "*") partestResources(dirs(kind), kind).get
          else (dirs(kind) * filter).get

        runPartestImpl(r, Map(kind -> files), o, s)
      }
    }
  }

  def partestRunnerTask(classpath: ScopedTask[Classpath], javacOptions: TaskKey[Seq[String]]): Project.Initialize[Task[PartestRunner]] =
   (classpath, javacOptions) map ((cp, opts) => new PartestRunner(Build.data(cp), opts mkString " "))
}

class PartestRunner(classpath: Seq[File], javaOpts: String) {
  // Classloader that does *not* have this as parent, for differing Scala version.
  lazy val classLoader = new java.net.URLClassLoader(classpath.map(_.toURI.toURL).toArray, null)
  lazy val (mainClass, mainMethod) = try {
    val c = classLoader.loadClass("scala.tools.partest.nest.SBTRunner")
    val m = c.getMethod("mainReflect", classOf[Array[String]])
    (c,m)
  }
  lazy val classPathArgs = Seq("-cp", classpath.map(_.getAbsoluteFile).mkString(java.io.File.pathSeparator))
  def run(args: Array[String]): java.util.Map[String,String] = try {
    // TODO - undo this settings after running.  Also globals are bad.
    System.setProperty("partest.java_opts", javaOpts)
    val allArgs = (classPathArgs ++ args).toArray
    mainMethod.invoke(null, allArgs).asInstanceOf[java.util.Map[String,String]]
  } catch {
    case e =>
    //error("Could not run Partest: " + e)
    throw e
  }
}