@@ -207,30 +207,6 @@ class CompilationTests extends ParallelTesting {
207207 compileDir(" ../library/src" ,
208208 allowDeepSubtypes.and(" -Ycheck-reentrant" , " -strict" , " -priorityclasspath" , defaultOutputDir))
209209
210- def sources (paths : JStream [Path ], excludedFiles : List [String ] = Nil ): List [String ] =
211- paths.iterator().asScala
212- .filter(path =>
213- (path.toString.endsWith(" .scala" ) || path.toString.endsWith(" .java" ))
214- && ! excludedFiles.contains(path.getFileName.toString))
215- .map(_.toString).toList
216-
217- val compilerDir = Paths .get(" ../compiler/src" )
218- val compilerSources = sources(Files .walk(compilerDir))
219-
220- val backendDir = Paths .get(" ../scala-backend/src/compiler/scala/tools/nsc/backend" )
221- val backendJvmDir = Paths .get(" ../scala-backend/src/compiler/scala/tools/nsc/backend/jvm" )
222-
223- // NOTE: Keep these exclusions synchronized with the ones in the sbt build (Build.scala)
224- val backendExcluded =
225- List (" JavaPlatform.scala" , " Platform.scala" , " ScalaPrimitives.scala" )
226- val backendJvmExcluded =
227- List (" BCodeICodeCommon.scala" , " GenASM.scala" , " GenBCode.scala" , " ScalacBackendInterface.scala" )
228-
229- val backendSources =
230- sources(Files .list(backendDir), excludedFiles = backendExcluded)
231- val backendJvmSources =
232- sources(Files .list(backendJvmDir), excludedFiles = backendJvmExcluded)
233-
234210 def dotty1 = {
235211 compileList(
236212 " dotty1" ,
@@ -261,6 +237,117 @@ class CompilationTests extends ParallelTesting {
261237 } :: Nil
262238 }.map(_.checkCompile()).foreach(_.delete())
263239 }
240+
241+ @ Test def bytecodeIdemporency : Unit = {
242+ var failed = 0
243+ var total = 0
244+ val blacklisted = Set (
245+ // Bridges on collections in different order. Second one in scala2 order.
246+ " pos/Map/scala/collection/immutable/Map" ,
247+ " pos/Map/scala/collection/immutable/AbstractMap" ,
248+ " pos/t1203a/NodeSeq" ,
249+ " pos/i2345/Whatever"
250+ )
251+ def checkIdempotency (): Unit = {
252+ val groupedBytecodeFiles : List [(Path , Path , Path , Path )] = {
253+ val bytecodeFiles = {
254+ def bytecodeFiles (paths : JStream [Path ]): List [Path ] = {
255+ def isBytecode (file : String ) = file.endsWith(" .class" ) || file.endsWith(" .tasty" )
256+ paths.iterator.asScala.filter(path => isBytecode(path.toString)).toList
257+ }
258+ val compilerDir1 = Paths .get(" ../out/idempotency1" )
259+ val compilerDir2 = Paths .get(" ../out/idempotency2" )
260+ bytecodeFiles(Files .walk(compilerDir1)) ++ bytecodeFiles(Files .walk(compilerDir2))
261+ }
262+ val groups = bytecodeFiles.groupBy(f => f.toString.substring(" ../out/idempotencyN/" .length, f.toString.length - 6 ))
263+ groups.filterNot(x => blacklisted(x._1)).valuesIterator.flatMap { g =>
264+ def pred (f : Path , i : Int , isTasty : Boolean ) =
265+ f.toString.contains(" idempotency" + i) && f.toString.endsWith(if (isTasty) " .tasty" else " .class" )
266+ val class1 = g.find(f => pred(f, 1 , isTasty = false ))
267+ val class2 = g.find(f => pred(f, 2 , isTasty = false ))
268+ val tasty1 = g.find(f => pred(f, 1 , isTasty = true ))
269+ val tasty2 = g.find(f => pred(f, 2 , isTasty = true ))
270+ assert(class1.isDefined, " Could not find class in idempotency1 for " + class2)
271+ assert(class2.isDefined, " Could not find class in idempotency2 for " + class1)
272+ if (tasty1.isEmpty || tasty2.isEmpty) Nil
273+ else List (Tuple4 (class1.get, tasty1.get, class2.get, tasty2.get))
274+ }.toList
275+ }
276+
277+ for ((class1, tasty1, class2, tasty2) <- groupedBytecodeFiles) {
278+ total += 1
279+ val bytes1 = Files .readAllBytes(class1)
280+ val bytes2 = Files .readAllBytes(class2)
281+ if (! java.util.Arrays .equals(bytes1, bytes2)) {
282+ failed += 1
283+ val tastyBytes1 = Files .readAllBytes(tasty1)
284+ val tastyBytes2 = Files .readAllBytes(tasty2)
285+ if (java.util.Arrays .equals(tastyBytes1, tastyBytes2))
286+ println(s " Idempotency test failed between $class1 and $class1 (same tasty) " )
287+ else
288+ println(s " Idempotency test failed between $tasty1 and $tasty2" )
289+ /* Dump bytes to console, could be useful if issue only appears in CI.
290+ * Create the .class locally with Files.write(path, Array[Byte](...)) with the printed array
291+ */
292+ // println(bytes1.mkString("Array[Byte](", ",", ")"))
293+ // println(bytes2.mkString("Array[Byte](", ",", ")"))
294+ }
295+ }
296+ }
297+
298+ val opt = defaultOptions.and(" -YemitTasty" )
299+
300+ def idempotency1 () = {
301+ compileList(" dotty1" , compilerSources ++ backendSources ++ backendJvmSources, opt) +
302+ compileFilesInDir(" ../tests/pos" , opt)
303+ }
304+ def idempotency2 () = {
305+ compileList(" dotty1" , compilerSources ++ backendSources ++ backendJvmSources, opt) +
306+ compileFilesInDir(" ../tests/pos" , opt)
307+ }
308+
309+ val tests = (idempotency1() + idempotency2()).keepOutput.checkCompile()
310+
311+ assert(new java.io.File (" ../out/idempotency1/" ).exists)
312+ assert(new java.io.File (" ../out/idempotency2/" ).exists)
313+
314+ val t0 = System .currentTimeMillis()
315+ checkIdempotency()
316+ println(s " checked bytecode idempotency ( ${(System .currentTimeMillis() - t0) / 1000.0 } sec) " )
317+
318+ tests.delete()
319+
320+ assert(failed == 0 , s " Failed $failed idempotency checks (out of $total) " )
321+ }
322+
323+
324+ private val (compilerSources, backendSources, backendJvmSources) = {
325+ def sources (paths : JStream [Path ], excludedFiles : List [String ] = Nil ): List [String ] =
326+ paths.iterator().asScala
327+ .filter(path =>
328+ (path.toString.endsWith(" .scala" ) || path.toString.endsWith(" .java" ))
329+ && ! excludedFiles.contains(path.getFileName.toString))
330+ .map(_.toString).toList
331+
332+ val compilerDir = Paths .get(" ../compiler/src" )
333+ val compilerSources0 = sources(Files .walk(compilerDir))
334+
335+ val backendDir = Paths .get(" ../scala-backend/src/compiler/scala/tools/nsc/backend" )
336+ val backendJvmDir = Paths .get(" ../scala-backend/src/compiler/scala/tools/nsc/backend/jvm" )
337+
338+ // NOTE: Keep these exclusions synchronized with the ones in the sbt build (Build.scala)
339+ val backendExcluded =
340+ List (" JavaPlatform.scala" , " Platform.scala" , " ScalaPrimitives.scala" )
341+ val backendJvmExcluded =
342+ List (" BCodeICodeCommon.scala" , " GenASM.scala" , " GenBCode.scala" , " ScalacBackendInterface.scala" )
343+
344+ val backendSources0 =
345+ sources(Files .list(backendDir), excludedFiles = backendExcluded)
346+ val backendJvmSources0 =
347+ sources(Files .list(backendJvmDir), excludedFiles = backendJvmExcluded)
348+
349+ (compilerSources0, backendSources0, backendJvmSources0)
350+ }
264351}
265352
266353object CompilationTests {
0 commit comments