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