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