@@ -5,6 +5,7 @@ package dotc
5
5
import org .junit .{ Test , BeforeClass , AfterClass }
6
6
7
7
import java .nio .file ._
8
+ import java .io .{ File => JFile }
8
9
import java .util .stream .{ Stream => JStream }
9
10
import scala .collection .JavaConverters ._
10
11
import scala .util .matching .Regex
@@ -231,30 +232,6 @@ class CompilationTests extends ParallelTesting {
231
232
compileDir(" ../library/src" ,
232
233
allowDeepSubtypes.and(" -Ycheck-reentrant" , " -strict" , " -priorityclasspath" , defaultOutputDir))
233
234
234
- def sources (paths : JStream [Path ], excludedFiles : List [String ] = Nil ): List [String ] =
235
- paths.iterator().asScala
236
- .filter(path =>
237
- (path.toString.endsWith(" .scala" ) || path.toString.endsWith(" .java" ))
238
- && ! excludedFiles.contains(path.getFileName.toString))
239
- .map(_.toString).toList
240
-
241
- val compilerDir = Paths .get(" ../compiler/src" )
242
- val compilerSources = sources(Files .walk(compilerDir))
243
-
244
- val backendDir = Paths .get(" ../scala-backend/src/compiler/scala/tools/nsc/backend" )
245
- val backendJvmDir = Paths .get(" ../scala-backend/src/compiler/scala/tools/nsc/backend/jvm" )
246
-
247
- // NOTE: Keep these exclusions synchronized with the ones in the sbt build (Build.scala)
248
- val backendExcluded =
249
- List (" JavaPlatform.scala" , " Platform.scala" , " ScalaPrimitives.scala" )
250
- val backendJvmExcluded =
251
- List (" BCodeICodeCommon.scala" , " GenASM.scala" , " GenBCode.scala" , " ScalacBackendInterface.scala" )
252
-
253
- val backendSources =
254
- sources(Files .list(backendDir), excludedFiles = backendExcluded)
255
- val backendJvmSources =
256
- sources(Files .list(backendJvmDir), excludedFiles = backendJvmExcluded)
257
-
258
235
def dotty1 = {
259
236
compileList(
260
237
" dotty1" ,
@@ -285,6 +262,115 @@ class CompilationTests extends ParallelTesting {
285
262
} :: Nil
286
263
}.map(_.checkCompile()).foreach(_.delete())
287
264
}
265
+
266
+ @ Test def bytecodeIdemporency : Unit = {
267
+ var failed = 0
268
+ var total = 0
269
+ val blacklisted = Set (
270
+ " pos/Map/scala/collection/immutable/Map" ,
271
+ " pos/Map/scala/collection/immutable/AbstractMap" ,
272
+ " pos/t1203a/NodeSeq"
273
+ )
274
+ def checkIdempotency (): Unit = {
275
+ def listFilesIn (i : Int ) = {
276
+ def getListOfFiles (file : JFile ): List [JFile ] =
277
+ if (file.isDirectory) file.listFiles.flatMap(getListOfFiles).toList
278
+ else if (file.toString.endsWith(" .class" ) || file.toString.endsWith(" .tasty" )) List (file)
279
+ else Nil
280
+ getListOfFiles(new JFile (s " ../out/idempotency $i" ))
281
+ }
282
+
283
+ def groupedFiles : List [(JFile , JFile , JFile , JFile )] = {
284
+ val files = listFilesIn(1 ) ++ listFilesIn(2 )
285
+ val groups = files.groupBy(f => f.toString.substring(" ../out/idempotencyN/" .length, f.toString.length - 6 ))
286
+ groups.filterNot(x => blacklisted(x._1)).valuesIterator.flatMap { g =>
287
+ def pred (f : JFile , i : Int , tasty : Boolean ) =
288
+ f.toString.contains(" idempotency" + i) && f.toString.endsWith(if (tasty) " .tasty" else " .class" )
289
+ val class1 = g.find(f => pred(f, 1 , false ))
290
+ val class2 = g.find(f => pred(f, 2 , false ))
291
+ val tasty1 = g.find(f => pred(f, 1 , true ))
292
+ val tasty2 = g.find(f => pred(f, 2 , true ))
293
+ assert(class1.isDefined, " Could not find class in idempotency1 for " + class2)
294
+ assert(class2.isDefined, " Could not find class in idempotency2 for " + class1)
295
+ if (tasty1.isEmpty || tasty2.isEmpty) Nil
296
+ else List (Tuple4 (class1.get, tasty1.get, class2.get, tasty2.get))
297
+ }.toList
298
+ }
299
+
300
+ for ((class1, tasty1, class2, tasty2) <- groupedFiles) {
301
+ total += 1
302
+ val bytes1 = Files .readAllBytes(class1.toPath)
303
+ val bytes2 = Files .readAllBytes(class2.toPath)
304
+ if (! java.util.Arrays .equals(bytes1, bytes2)) {
305
+ failed += 1
306
+ val tastyBytes1 = Files .readAllBytes(tasty1.toPath)
307
+ val tastyBytes2 = Files .readAllBytes(tasty2.toPath)
308
+ if (java.util.Arrays .equals(tastyBytes1, tastyBytes2))
309
+ println(s " Idempotency test failed between $class1 and $class1 (same tasty) " )
310
+ else
311
+ println(s " Idempotency test failed between $tasty1 and $tasty2" )
312
+ /* Dump bytes to console, could be useful if issue only appears in CI.
313
+ * Create the .class locally with Files.write(path, Array[Byte](...)) with the printed array
314
+ */
315
+ // println(bytes1.mkString("Array[Byte](", ",", ")"))
316
+ // println(bytes2.mkString("Array[Byte](", ",", ")"))
317
+ }
318
+ }
319
+ }
320
+
321
+ val opt = defaultOptions.and(" -YemitTasty" )
322
+
323
+ def idempotency1 () = {
324
+ compileList(" dotty1" , compilerSources ++ backendSources ++ backendJvmSources, opt) +
325
+ compileFilesInDir(" ../tests/pos" , opt)
326
+ }
327
+ def idempotency2 () = {
328
+ compileList(" dotty1" , compilerSources ++ backendSources ++ backendJvmSources, opt) +
329
+ compileFilesInDir(" ../tests/pos" , opt)
330
+ }
331
+
332
+ val tests = (idempotency1() + idempotency2()).keepOutput.checkCompile()
333
+
334
+ assert(new java.io.File (" ../out/idempotency1/" ).exists)
335
+ assert(new java.io.File (" ../out/idempotency2/" ).exists)
336
+
337
+ val t0 = System .currentTimeMillis()
338
+ checkIdempotency()
339
+ println(s " checked bytecode idempotency ( ${(System .currentTimeMillis() - t0) / 1000.0 } sec) " )
340
+
341
+ tests.delete()
342
+
343
+ assert(failed == 0 , s " Failed $failed idempotency checks (out of $total) " )
344
+ }
345
+
346
+
347
+ private val (compilerSources, backendSources, backendJvmSources) = {
348
+ def sources (paths : JStream [Path ], excludedFiles : List [String ] = Nil ): List [String ] =
349
+ paths.iterator().asScala
350
+ .filter(path =>
351
+ (path.toString.endsWith(" .scala" ) || path.toString.endsWith(" .java" ))
352
+ && ! excludedFiles.contains(path.getFileName.toString))
353
+ .map(_.toString).toList
354
+
355
+ val compilerDir = Paths .get(" ../compiler/src" )
356
+ val compilerSources0 = sources(Files .walk(compilerDir))
357
+
358
+ val backendDir = Paths .get(" ../scala-backend/src/compiler/scala/tools/nsc/backend" )
359
+ val backendJvmDir = Paths .get(" ../scala-backend/src/compiler/scala/tools/nsc/backend/jvm" )
360
+
361
+ // NOTE: Keep these exclusions synchronized with the ones in the sbt build (Build.scala)
362
+ val backendExcluded =
363
+ List (" JavaPlatform.scala" , " Platform.scala" , " ScalaPrimitives.scala" )
364
+ val backendJvmExcluded =
365
+ List (" BCodeICodeCommon.scala" , " GenASM.scala" , " GenBCode.scala" , " ScalacBackendInterface.scala" )
366
+
367
+ val backendSources0 =
368
+ sources(Files .list(backendDir), excludedFiles = backendExcluded)
369
+ val backendJvmSources0 =
370
+ sources(Files .list(backendJvmDir), excludedFiles = backendJvmExcluded)
371
+
372
+ (compilerSources0, backendSources0, backendJvmSources0)
373
+ }
288
374
}
289
375
290
376
object CompilationTests {
0 commit comments