5
5
package scala .tools .partest
6
6
package nest
7
7
8
- import java .io .{ Console => _ , _ }
8
+ import java .io .{Console => _ , _ }
9
+ import java .lang .reflect .InvocationTargetException
10
+ import java .nio .charset .Charset
11
+ import java .nio .file .{Files , StandardOpenOption }
9
12
import java .util .concurrent .Executors
10
13
import java .util .concurrent .TimeUnit
11
14
import java .util .concurrent .TimeUnit .NANOSECONDS
15
+
12
16
import scala .collection .mutable .ListBuffer
13
17
import scala .concurrent .duration .Duration
14
18
import scala .reflect .internal .FatalError
15
19
import scala .reflect .internal .util .ScalaClassLoader
16
- import scala .sys .process .{ Process , ProcessLogger }
17
- import scala .tools .nsc .Properties .{ envOrNone , isWin , javaHome , propOrEmpty , versionMsg , javaVmName , javaVmVersion , javaVmInfo }
18
- import scala .tools .nsc .{ Settings , CompilerCommand , Global }
20
+ import scala .sys .process .{Process , ProcessLogger }
21
+ import scala .tools .nsc .Properties .{envOrNone , isWin , javaHome , javaVmInfo , javaVmName , javaVmVersion , propOrEmpty , versionMsg }
22
+ import scala .tools .nsc .{CompilerCommand , Global , Settings }
19
23
import scala .tools .nsc .reporters .ConsoleReporter
20
24
import scala .tools .nsc .util .stackTraceString
21
- import scala .util .{ Try , Success , Failure }
25
+ import scala .util .{Failure , Success , Try }
22
26
import ClassPath .join
23
- import TestState .{ Pass , Fail , Crash , Uninitialized , Updated }
24
-
25
- import FileManager .{ compareContents , joinPaths , withTempFile }
27
+ import TestState .{Crash , Fail , Pass , Uninitialized , Updated }
28
+ import FileManager .{compareContents , joinPaths , withTempFile }
29
+ import scala .reflect .internal .util .ScalaClassLoader .URLClassLoader
30
+ import scala .util .control .ControlThrowable
26
31
27
32
trait TestInfo {
28
33
/** pos/t1234 */
@@ -53,6 +58,7 @@ trait TestInfo {
53
58
54
59
/** Run a single test. Rubber meets road. */
55
60
class Runner (val testFile : File , val suiteRunner : SuiteRunner , val nestUI : NestUI ) extends TestInfo {
61
+ private val stopwatch = new Stopwatch ()
56
62
57
63
import suiteRunner .{fileManager => fm , _ }
58
64
val fileManager = fm
@@ -125,9 +131,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
125
131
)
126
132
127
133
pushTranscript(args mkString " " )
128
- val captured = StreamCapture (runCommand(args, logFile))
129
- if (captured.result) genPass() else {
130
- logFile appendAll captured.stderr
134
+ if (runCommand(args, logFile)) genPass() else {
131
135
genFail(" java compilation failed" )
132
136
}
133
137
}
@@ -157,8 +161,6 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
157
161
if (javaopts.nonEmpty)
158
162
nestUI.verbose(s " Found javaopts file ' $argsFile', using options: ' ${javaopts.mkString(" ," )}' " )
159
163
160
- val testFullPath = testFile.getAbsolutePath
161
-
162
164
// Note! As this currently functions, suiteRunner.javaOpts must precede argString
163
165
// because when an option is repeated to java only the last one wins.
164
166
// That means until now all the .javaopts files were being ignored because
@@ -167,30 +169,15 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
167
169
//
168
170
// debug: Found javaopts file 'files/shootout/message.scala-2.javaopts', using options: '-Xss32k'
169
171
// debug: java -Xss32k -Xss2m -Xms256M -Xmx1024M -classpath [...]
170
- val extras = if (nestUI.debug) List (" -Dpartest.debug=true" ) else Nil
171
- val propertyOptions = List (
172
- " -Dfile.encoding=UTF-8" ,
173
- " -Djava.library.path=" + logFile.getParentFile.getAbsolutePath,
174
- " -Dpartest.output=" + outDir.getAbsolutePath,
175
- " -Dpartest.lib=" + libraryUnderTest.getAbsolutePath,
176
- " -Dpartest.reflect=" + reflectUnderTest.getAbsolutePath,
177
- " -Dpartest.comp=" + compilerUnderTest.getAbsolutePath,
178
- " -Dpartest.cwd=" + outDir.getParent,
179
- " -Dpartest.test-path=" + testFullPath,
180
- " -Dpartest.testname=" + fileBase,
181
- " -Djavacmd=" + javaCmdPath,
182
- " -Djavaccmd=" + javacCmdPath,
183
- " -Duser.language=en" ,
184
- " -Duser.country=US"
185
- ) ++ extras
172
+ val propertyOpts = propertyOptions(fork = true ).map { case (k, v) => s " -D $k= $v" }
186
173
187
174
val classpath = joinPaths(extraClasspath ++ testClassPath)
188
175
189
176
javaCmdPath +: (
190
177
(suiteRunner.javaOpts.split(' ' ) ++ extraJavaOptions ++ javaopts).filter(_ != " " ).toList ++ Seq (
191
178
" -classpath" ,
192
179
join(outDir.toString, classpath)
193
- ) ++ propertyOptions ++ Seq (
180
+ ) ++ propertyOpts ++ Seq (
194
181
" scala.tools.nsc.MainGenericRunner" ,
195
182
" -usejavacp" ,
196
183
" Test" ,
@@ -199,6 +186,40 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
199
186
)
200
187
}
201
188
189
+ def propertyOptions (fork : Boolean ): List [(String , String )] = {
190
+ val testFullPath = testFile.getAbsolutePath
191
+ val extras = if (nestUI.debug) List (" partest.debug" -> " true" ) else Nil
192
+ val immutablePropsToCheck = List [(String , String )](
193
+ " file.encoding" -> " UTF-8" ,
194
+ " user.language" -> " en" ,
195
+ " user.country" -> " US"
196
+ )
197
+ val immutablePropsForkOnly = List [(String , String )](
198
+ " java.library.path" -> logFile.getParentFile.getAbsolutePath,
199
+ )
200
+ val shared = List (
201
+ " partest.output" -> (" " + outDir.getAbsolutePath),
202
+ " partest.lib" -> (" " + libraryUnderTest.jfile.getAbsolutePath),
203
+ " partest.reflect" -> (" " + reflectUnderTest.jfile.getAbsolutePath),
204
+ " partest.comp" -> (" " + compilerUnderTest.jfile.getAbsolutePath),
205
+ " partest.cwd" -> (" " + outDir.getParent),
206
+ " partest.test-path" -> (" " + testFullPath),
207
+ " partest.testname" -> (" " + fileBase),
208
+ " javacmd" -> (" " + javaCmdPath),
209
+ " javaccmd" -> (" " + javacCmdPath),
210
+ ) ++ extras
211
+ if (fork) {
212
+ immutablePropsToCheck ++ immutablePropsForkOnly ++ shared
213
+ } else {
214
+ for ((k, requiredValue) <- immutablePropsToCheck) {
215
+ val actual = System .getProperty(k)
216
+ assert(actual == requiredValue, s " Unable to run test without forking as the current JVM has an incorrect system property. For $k, found $actual, required $requiredValue" )
217
+ }
218
+ shared
219
+ }
220
+ }
221
+
222
+
202
223
/** Runs command redirecting standard out and
203
224
* error out to output file.
204
225
*/
@@ -235,6 +256,50 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
235
256
}
236
257
}
237
258
259
+ def execTestInProcess (classesDir : File , log : File ): Boolean = {
260
+ stopwatch.pause()
261
+ suiteRunner.synchronized {
262
+ stopwatch.start()
263
+ def run (): Unit = {
264
+ StreamCapture .withExtraProperties(propertyOptions(fork = false ).toMap) {
265
+ try {
266
+ val out = Files .newOutputStream(log.toPath, StandardOpenOption .APPEND )
267
+ try {
268
+ val loader = new URLClassLoader (classesDir.toURI.toURL :: Nil , getClass.getClassLoader)
269
+ StreamCapture .capturingOutErr(out) {
270
+ val cls = loader.loadClass(" Test" )
271
+ val main = cls.getDeclaredMethod(" main" , classOf [Array [String ]])
272
+ try {
273
+ main.invoke(null , Array [String ](" jvm" ))
274
+ } catch {
275
+ case ite : InvocationTargetException => throw ite.getCause
276
+ }
277
+ }
278
+ } finally {
279
+ out.close()
280
+ }
281
+ } catch {
282
+ case t : ControlThrowable => throw t
283
+ case t : Throwable =>
284
+ // We'll let the checkfile diffing report this failure
285
+ Files .write(log.toPath, stackTraceString(t).getBytes(Charset .defaultCharset()), StandardOpenOption .APPEND )
286
+ }
287
+ }
288
+ }
289
+
290
+ pushTranscript(s " <in process execution of $testIdent> > ${logFile.getName}" )
291
+
292
+ TrapExit (() => run()) match {
293
+ case Left ((status, throwable)) if status != 0 =>
294
+ setLastState(genFail(" non-zero exit code" ))
295
+ false
296
+ case _ =>
297
+ setLastState(genPass())
298
+ true
299
+ }
300
+ }
301
+ }
302
+
238
303
override def toString = s """ Test( $testIdent, lastState = $lastState) """
239
304
240
305
// result is unused
@@ -641,9 +706,10 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
641
706
(diffIsOk, LogContext (logFile, swr, wr))
642
707
}
643
708
644
- def run (): TestState = {
709
+ def run (): ( TestState , Long ) = {
645
710
// javac runner, for one, would merely append to an existing log file, so just delete it before we start
646
711
logFile.delete()
712
+ stopwatch.start()
647
713
648
714
if (kind == " neg" || (kind endsWith " -neg" )) runNegTest()
649
715
else kind match {
@@ -652,10 +718,18 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
652
718
case " res" => runResidentTest()
653
719
case " scalap" => runScalapTest()
654
720
case " script" => runScriptTest()
655
- case _ => runTestCommon(execTest(outDir, logFile) && diffIsOk )
721
+ case _ => runRunTest( )
656
722
}
657
723
658
- lastState
724
+ (lastState, stopwatch.stop)
725
+ }
726
+
727
+ private def runRunTest (): Unit = {
728
+ val argsFile = testFile changeExtension " javaopts"
729
+ val javaopts = readOptionsFile(argsFile)
730
+ val execInProcess = PartestDefaults .execInProcess && javaopts.isEmpty && ! Set (" specialized" , " instrumented" ).contains(testFile.getParentFile.getName)
731
+ def exec () = if (execInProcess) execTestInProcess(outDir, logFile) else execTest(outDir, logFile)
732
+ runTestCommon(exec() && diffIsOk)
659
733
}
660
734
661
735
private def decompileClass (clazz : Class [_], isPackageObject : Boolean ): String = {
@@ -738,6 +812,8 @@ class SuiteRunner(
738
812
// TODO: make this immutable
739
813
PathSettings .testSourcePath = testSourcePath
740
814
815
+ val durations = collection.concurrent.TrieMap [File , Long ]()
816
+
741
817
def banner = {
742
818
val baseDir = fileManager.compilerUnderTest.parent.toString
743
819
def relativize (path : String ) = path.replace(baseDir, s " $$ baseDir " ).replace(PathSettings .srcDir.toString, " $sourceDir" )
@@ -759,29 +835,35 @@ class SuiteRunner(
759
835
// |Java Classpath: ${sys.props("java.class.path")}
760
836
}
761
837
762
- def onFinishTest (testFile : File , result : TestState , durationMs : Long ): TestState = result
838
+ def onFinishTest (testFile : File , result : TestState , durationMs : Long ): TestState = {
839
+ durations(testFile) = durationMs
840
+ result
841
+ }
763
842
764
843
def runTest (testFile : File ): TestState = {
765
844
val start = System .nanoTime()
766
845
val runner = new Runner (testFile, this , nestUI)
846
+ var stopwatchDuration : Option [Long ] = None
767
847
768
848
// when option "--failed" is provided execute test only if log
769
849
// is present (which means it failed before)
770
850
val state =
771
851
if (failed && ! runner.logFile.canRead)
772
852
runner.genPass()
773
853
else {
774
- val (state, _ ) =
775
- try timed( runner.run() )
854
+ val (state, durationMs ) =
855
+ try runner.run()
776
856
catch {
777
857
case t : Throwable => throw new RuntimeException (s " Error running $testFile" , t)
778
858
}
779
- nestUI.reportTest(state, runner)
859
+ stopwatchDuration = Some (durationMs)
860
+ nestUI.reportTest(state, runner, durationMs)
780
861
runner.cleanup()
781
862
state
782
863
}
783
864
val end = System .nanoTime()
784
- onFinishTest(testFile, state, TimeUnit .NANOSECONDS .toMillis(end - start))
865
+ val durationMs = stopwatchDuration.getOrElse(TimeUnit .NANOSECONDS .toMillis(end - start))
866
+ onFinishTest(testFile, state, durationMs)
785
867
}
786
868
787
869
def runTestsForFiles (kindFiles : Array [File ], kind : String ): Array [TestState ] = {
0 commit comments