@@ -207,30 +207,6 @@ class CompilationTests extends ParallelTesting {
207
207
compileDir(" ../library/src" ,
208
208
allowDeepSubtypes.and(" -Ycheck-reentrant" , " -strict" , " -priorityclasspath" , defaultOutputDir))
209
209
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
-
234
210
def dotty1 = {
235
211
compileList(
236
212
" dotty1" ,
@@ -261,6 +237,117 @@ class CompilationTests extends ParallelTesting {
261
237
} :: Nil
262
238
}.map(_.checkCompile()).foreach(_.delete())
263
239
}
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
+ }
264
351
}
265
352
266
353
object CompilationTests {
0 commit comments