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