summaryrefslogtreecommitdiff
path: root/src/partest-alternative/scala/tools/partest/Actions.scala
blob: 9a64edeadc2abdefa264ec310cc6ee94a6627ae1 (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
178
179
180
181
182
183
184
185
186
187
188
189
/*                     __                                               *\
**     ________ ___   / /  ___     Scala Parallel Testing               **
**    / __/ __// _ | / /  / _ |    (c) 2007-2011, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scala.tools
package partest

import util._
import nsc.io._
import scala.sys.process._

trait Actions {
  partest: Universe =>

  class TestSequence(val actions: List[TestStep]) extends AbsTestSequence {
  }

  implicit def createSequence(xs: List[TestStep]) = new TestSequence(xs)

  trait ExecSupport {
    self: TestEntity =>

    def execEnv: Map[String, String] = {
      val map = assembleEnvironment()
      val cwd = execCwd.toList map ("CWD" -> _.path)

      map ++ cwd
    }
    def execCwd = if (commandFile.isFile) Some(sourcesDir) else None

    def runExec(args: List[String]): Boolean = {
      val cmd     = fromArgs(args)

      if (isVerbose) {
        trace("runExec: " + execEnv.mkString("ENV(", "\n", "\n)"))
        execCwd foreach (x => trace("CWD(" + x + ")"))
      }

      trace("runExec: " + cmd)
      isDryRun || execAndLog(cmd)
    }

    /** Exec a process to run a command.  Assumes 0 exit value is success.
     *  Of necessity, also treats no available exit value as success.
     */
    protected def execAndLog(cmd: String) = (cmd #> logFile.jfile !) == 0
  }

  trait ScriptableTest {
    self: TestEntity =>

    /** Translates a line from a .cmds file into a teststep.
     */
    def customTestStep(line: String): TestStep = {
      trace("customTestStep: " + line)
      val (cmd, rest) = line span (x => !Character.isWhitespace(x))
      def qualify(name: String) = sourcesDir / name path
      val args = toArgs(rest) map qualify
      def fail: TestStep = (_: TestEntity) => error("Parse error: did not understand '%s'" format line)

      val f: TestEntity => Boolean = cmd match {
        case "scalac"   => _ scalac args
        case "javac"    => _ javac args
        case "scala"    => _ runScala args
        case _          => fail
      }
      f
    }
  }

  trait CompilableTest extends CompileExecSupport {
    self: TestEntity =>

    def sourceFiles   = location.walk collect { case f: File if isJavaOrScala(f) => f } toList
    def allSources    = sourceFiles map (_.path)
    def scalaSources  = sourceFiles filter isScala map (_.path)
    def javaSources   = sourceFiles filter isJava map (_.path)

    /** If there are mixed java and scala files, the standard compilation
     *  sequence is:
     *
     *    scalac with all files
     *    javac with only java files
     *    scalac with only scala files
     *
     *  This should be expanded to encompass other strategies so we know how
     *  well they're working or not working - notably, it would be very useful
     *  to know exactly when and how two-pass compilation fails.
     */
    def compile() = {
      trace("compile: " + sourceFiles)

      def compileJava()   = javac(javaSources)
      def compileScala()  = scalac(scalaSources)
      def compileAll()    = scalac(allSources)
      def compileMixed()  = compileAll() && compileJava() && compileScala()

      if (scalaSources.nonEmpty && javaSources.nonEmpty) compileMixed()
      else compileScala()
    }
  }

  trait DiffableTest {
    self: TestEntity =>

    def checkFile: File   = withExtension("check").toFile
    def checkFileRequired =
      returning(checkFile.isFile)(res => if (!res) warnAndLog("A checkFile at '%s' is mandatory.\n" format checkFile.path))

    lazy val sourceFileNames = sourceFiles map (_.name)

    /** Given the difficulty of verifying that any selective approach works
     *  everywhere, the algorithm now is to look for the name of any known
     *  source file for this test, and if seen, remove all the non-whitespace
     *  preceding it.  (Paths with whitespace don't work anyway.) This should
     *  wipe out all slashes, backslashes, C:\, cygwin/windows differences,
     *  and whatever else makes a simple diff not simple.
     *
     *  The log and check file are both transformed, which I don't think is
     *  correct -- only the log should be -- but doing it this way until I
     *  can clarify martin's comments in #3283.
     */
    def normalizePaths(s: String) =
      sourceFileNames.foldLeft(s)((res, name) => res.replaceAll("""\S+\Q%s\E""" format name, name))

    /** The default cleanup normalizes paths relative to sourcesDir,
     *  absorbs line terminator differences by going to lines and back,
     *  and trims leading or trailing whitespace.
     */
    def diffCleanup(f: File) = safeLines(f) map normalizePaths mkString "\n" trim

    /** diffFiles requires actual Files as arguments but the output we want
     *  is the post-processed versions of log/check, so we resort to tempfiles.
     */
    lazy val diffOutput = {
      if (!checkFile.exists) "" else {
        val input   = diffCleanup(checkFile)
        val output  = diffCleanup(logFile)
        def asFile(s: String) = returning(File.makeTemp("partest-diff"))(_ writeAll s)

        if (input == output) ""
        else diffFiles(asFile(input), asFile(output))
      }
    }
    private def checkTraceName  = tracePath(checkFile)
    private def logTraceName    = tracePath(logFile)
    private def isDiffConfirmed = checkFile.exists && (diffOutput == "")

    private def sendTraceMsg() {
      def result =
        if (isDryRun) ""
        else if (isDiffConfirmed) " [passed]"
        else if (checkFile.exists) " [failed]"
        else " [unchecked]"

      trace("diff %s %s%s".format(checkTraceName, logTraceName, result))
    }

    /** If optional is true, a missing check file is considered
     *  a successful diff.  Necessary since many categories use
     *  checkfiles in an ad hoc manner.
     */
    def runDiff() = {
      sendTraceMsg()

      def updateCheck = (
        isUpdateCheck && {
          val formatStr = "** diff %s %s: " + (
            if (checkFile.exists) "failed, updating '%s' and marking as passed."
            else if (diffOutput == "") "not creating checkFile at '%s' as there is no output."
            else "was unchecked, creating '%s' for future tests."
          ) + "\n"

          normal(formatStr.format(checkTraceName, logTraceName, checkFile.path))
          if (diffOutput != "") normal(diffOutput)

          checkFile.writeAll(diffCleanup(logFile), "\n")
          true
        }
      )

      isDryRun || isDiffConfirmed || (updateCheck || !checkFile.exists)
    }
  }
}