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
@@ -157,8 +163,6 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
157
163
if (javaopts.nonEmpty)
158
164
nestUI.verbose(s " Found javaopts file ' $argsFile', using options: ' ${javaopts.mkString(" ," )}' " )
159
165
160
- val testFullPath = testFile.getAbsolutePath
161
-
162
166
// Note! As this currently functions, suiteRunner.javaOpts must precede argString
163
167
// because when an option is repeated to java only the last one wins.
164
168
// That means until now all the .javaopts files were being ignored because
@@ -167,30 +171,15 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
167
171
//
168
172
// debug: Found javaopts file 'files/shootout/message.scala-2.javaopts', using options: '-Xss32k'
169
173
// 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
174
+ val propertyOpts = propertyOptions(fork = true ).map { case (k, v) => s " -D $k= $v" }
186
175
187
176
val classpath = joinPaths(extraClasspath ++ testClassPath)
188
177
189
178
javaCmdPath +: (
190
179
(suiteRunner.javaOpts.split(' ' ) ++ extraJavaOptions ++ javaopts).filter(_ != " " ).toList ++ Seq (
191
180
" -classpath" ,
192
181
join(outDir.toString, classpath)
193
- ) ++ propertyOptions ++ Seq (
182
+ ) ++ propertyOpts ++ Seq (
194
183
" scala.tools.nsc.MainGenericRunner" ,
195
184
" -usejavacp" ,
196
185
" Test" ,
@@ -199,6 +188,40 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
199
188
)
200
189
}
201
190
191
+ def propertyOptions (fork : Boolean ): List [(String , String )] = {
192
+ val testFullPath = testFile.getAbsolutePath
193
+ val extras = if (nestUI.debug) List (" partest.debug" -> " true" ) else Nil
194
+ val immutablePropsToCheck = List [(String , String )](
195
+ " file.encoding" -> " UTF-8" ,
196
+ " user.language" -> " en" ,
197
+ " user.country" -> " US"
198
+ )
199
+ val immutablePropsForkOnly = List [(String , String )](
200
+ " java.library.path" -> logFile.getParentFile.getAbsolutePath,
201
+ )
202
+ val shared = List (
203
+ " partest.output" -> (" " + outDir.getAbsolutePath),
204
+ " partest.lib" -> (" " + libraryUnderTest.jfile.getAbsolutePath),
205
+ " partest.reflect" -> (" " + reflectUnderTest.jfile.getAbsolutePath),
206
+ " partest.comp" -> (" " + compilerUnderTest.jfile.getAbsolutePath),
207
+ " partest.cwd" -> (" " + outDir.getParent),
208
+ " partest.test-path" -> (" " + testFullPath),
209
+ " partest.testname" -> (" " + fileBase),
210
+ " javacmd" -> (" " + javaCmdPath),
211
+ " javaccmd" -> (" " + javacCmdPath),
212
+ ) ++ extras
213
+ if (fork) {
214
+ immutablePropsToCheck ++ immutablePropsForkOnly ++ shared
215
+ } else {
216
+ for ((k, requiredValue) <- immutablePropsToCheck) {
217
+ val actual = System .getProperty(k)
218
+ 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" )
219
+ }
220
+ shared
221
+ }
222
+ }
223
+
224
+
202
225
/** Runs command redirecting standard out and
203
226
* error out to output file.
204
227
*/
@@ -235,6 +258,53 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
235
258
}
236
259
}
237
260
261
+ def execTestInProcess (classesDir : File , log : File ): Boolean = {
262
+ stopwatch.pause()
263
+ suiteRunner.synchronized {
264
+ stopwatch.start()
265
+ def run (): Unit = {
266
+ StreamCapture .withExtraProperties(propertyOptions(fork = false ).toMap) {
267
+ try {
268
+ val out = Files .newOutputStream(log.toPath, StandardOpenOption .APPEND )
269
+ try {
270
+ val loader = new URLClassLoader (classesDir.toURI.toURL :: Nil , getClass.getClassLoader)
271
+ StreamCapture .capturingOutErr(out) {
272
+ val cls = loader.loadClass(" Test" )
273
+ val main = cls.getDeclaredMethod(" main" , classOf [Array [String ]])
274
+ try {
275
+ main.invoke(null , Array [String ](" jvm" ))
276
+ } catch {
277
+ case ite : InvocationTargetException => throw ite.getCause
278
+ }
279
+ }
280
+ } finally {
281
+ out.close()
282
+ }
283
+ } catch {
284
+ case t : ControlThrowable => throw t
285
+ case t : Throwable =>
286
+ // We'll let the checkfile diffing report this failure
287
+ Files .write(log.toPath, stackTraceString(t).getBytes(Charset .defaultCharset()), StandardOpenOption .APPEND )
288
+ }
289
+ }
290
+ }
291
+
292
+ pushTranscript(s " <in process execution of $testIdent> > ${logFile.getName}" )
293
+
294
+ TrapExit (() => run()) match {
295
+ case Left ((status, throwable)) if status != 0 =>
296
+ // Files.readAllLines(log.toPath).forEach(println(_))
297
+ // val error = new AssertionError(s"System.exit(${status}) was called.")
298
+ // error.setStackTrace(throwable.getStackTrace)
299
+ setLastState(genFail(" non-zero exit code" ))
300
+ false
301
+ case _ =>
302
+ setLastState(genPass())
303
+ true
304
+ }
305
+ }
306
+ }
307
+
238
308
override def toString = s """ Test( $testIdent, lastState = $lastState) """
239
309
240
310
// result is unused
@@ -641,9 +711,10 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
641
711
(diffIsOk, LogContext (logFile, swr, wr))
642
712
}
643
713
644
- def run (): TestState = {
714
+ def run (): ( TestState , Long ) = {
645
715
// javac runner, for one, would merely append to an existing log file, so just delete it before we start
646
716
logFile.delete()
717
+ stopwatch.start()
647
718
648
719
if (kind == " neg" || (kind endsWith " -neg" )) runNegTest()
649
720
else kind match {
@@ -652,10 +723,18 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
652
723
case " res" => runResidentTest()
653
724
case " scalap" => runScalapTest()
654
725
case " script" => runScriptTest()
655
- case _ => runTestCommon(execTest(outDir, logFile) && diffIsOk )
726
+ case _ => runRunTest( )
656
727
}
657
728
658
- lastState
729
+ (lastState, stopwatch.stop)
730
+ }
731
+
732
+ private def runRunTest (): Unit = {
733
+ val argsFile = testFile changeExtension " javaopts"
734
+ val javaopts = readOptionsFile(argsFile)
735
+ val execInProcess = PartestDefaults .execInProcess && javaopts.isEmpty && ! Set (" specialized" , " instrumented" ).contains(testFile.getParentFile.getName)
736
+ def exec () = if (execInProcess) execTestInProcess(outDir, logFile) else execTest(outDir, logFile)
737
+ runTestCommon(exec() && diffIsOk)
659
738
}
660
739
661
740
private def decompileClass (clazz : Class [_], isPackageObject : Boolean ): String = {
@@ -738,6 +817,8 @@ class SuiteRunner(
738
817
// TODO: make this immutable
739
818
PathSettings .testSourcePath = testSourcePath
740
819
820
+ val durations = collection.concurrent.TrieMap [File , Long ]()
821
+
741
822
def banner = {
742
823
val baseDir = fileManager.compilerUnderTest.parent.toString
743
824
def relativize (path : String ) = path.replace(baseDir, s " $$ baseDir " ).replace(PathSettings .srcDir.toString, " $sourceDir" )
@@ -759,29 +840,35 @@ class SuiteRunner(
759
840
// |Java Classpath: ${sys.props("java.class.path")}
760
841
}
761
842
762
- def onFinishTest (testFile : File , result : TestState , durationMs : Long ): TestState = result
843
+ def onFinishTest (testFile : File , result : TestState , durationMs : Long ): TestState = {
844
+ durations(testFile) = durationMs
845
+ result
846
+ }
763
847
764
848
def runTest (testFile : File ): TestState = {
765
849
val start = System .nanoTime()
766
850
val runner = new Runner (testFile, this , nestUI)
851
+ var stopwatchDuration : Option [Long ] = None
767
852
768
853
// when option "--failed" is provided execute test only if log
769
854
// is present (which means it failed before)
770
855
val state =
771
856
if (failed && ! runner.logFile.canRead)
772
857
runner.genPass()
773
858
else {
774
- val (state, _ ) =
775
- try timed( runner.run() )
859
+ val (state, durationMs ) =
860
+ try runner.run()
776
861
catch {
777
862
case t : Throwable => throw new RuntimeException (s " Error running $testFile" , t)
778
863
}
779
- nestUI.reportTest(state, runner)
864
+ stopwatchDuration = Some (durationMs)
865
+ nestUI.reportTest(state, runner, durationMs)
780
866
runner.cleanup()
781
867
state
782
868
}
783
869
val end = System .nanoTime()
784
- onFinishTest(testFile, state, TimeUnit .NANOSECONDS .toMillis(end - start))
870
+ val durationMs = stopwatchDuration.getOrElse(TimeUnit .NANOSECONDS .toMillis(end - start))
871
+ onFinishTest(testFile, state, durationMs)
785
872
}
786
873
787
874
def runTestsForFiles (kindFiles : Array [File ], kind : String ): Array [TestState ] = {
0 commit comments