Skip to content

Commit c4550da

Browse files
committed
Add decompiler tests
1 parent 3004af3 commit c4550da

File tree

10 files changed

+251
-81
lines changed

10 files changed

+251
-81
lines changed
Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package dotty.tools.dotc
22
package decompiler
33

4+
import java.io.{OutputStream, PrintStream}
5+
46
import dotty.tools.dotc.core.Contexts._
57
import dotty.tools.dotc.core.Phases.Phase
68
import dotty.tools.dotc.core.tasty.TastyPrinter
9+
import dotty.tools.io.{File, Path}
710

811
/** Phase that prints the trees in all loaded compilation units.
912
*
@@ -14,23 +17,39 @@ class DecompilationPrinter extends Phase {
1417
override def phaseName: String = "decompilationPrinter"
1518

1619
override def run(implicit ctx: Context): Unit = {
17-
val unit = ctx.compilationUnit
20+
val outputDir = ctx.settings.outputDir.value
21+
if (outputDir == ".") printToOutput(System.out)
22+
else {
23+
var os: OutputStream = null
24+
var ps: PrintStream = null
25+
try {
26+
os = File(outputDir + ".decompiled").outputStream()
27+
ps = new PrintStream(os)
28+
printToOutput(ps)
29+
} finally {
30+
if (os ne null) os.close()
31+
if (ps ne null) ps.close()
32+
}
33+
}
34+
}
1835

36+
private def printToOutput(out: PrintStream)(implicit ctx: Context): Unit = {
37+
val unit = ctx.compilationUnit
1938
val pageWidth = ctx.settings.pageWidth.value
2039

2140
val doubleLine = "=" * pageWidth
2241
val line = "-" * pageWidth
2342

24-
println(doubleLine)
25-
println(unit.source)
26-
println(line)
43+
out.println(doubleLine)
44+
out.println(unit.source)
45+
out.println(line)
2746

28-
println(unit.tpdTree.show)
29-
println(line)
47+
out.println(unit.tpdTree.show)
48+
out.println(line)
3049

3150
if (ctx.settings.printTasty.value) {
3251
new TastyPrinter(unit.pickled.head._2).printContents()
33-
println(line)
52+
out.println(line)
3453
}
3554
}
3655
}

compiler/test/dotty/tools/dotc/FromTastyTests.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class FromTastyTests extends ParallelTesting {
2222

2323
@Test def posTestFromTasty: Unit = {
2424
implicit val testGroup: TestGroup = TestGroup("posTestFromTasty")
25-
val (step1, step2) = compileTastyInDir("../tests/pos", defaultOptions,
25+
val (step1, step2, step3) = compileTastyInDir("../tests/pos", defaultOptions,
2626
blacklist = Set(
2727
"Meter.scala",
2828
"NoCyclicReference.scala",
@@ -56,12 +56,13 @@ class FromTastyTests extends ParallelTesting {
5656
)
5757
step1.checkCompile() // Compile all files to generate the class files with tasty
5858
step2.checkCompile() // Compile from tasty
59-
(step1 + step2).delete()
59+
step3.checkCompile() // Decompile from tasty
60+
(step1 + step2 + step3).delete()
6061
}
6162

6263
@Test def runTestFromTasty: Unit = {
6364
implicit val testGroup: TestGroup = TestGroup("runTestFromTasty")
64-
val (step1, step2) = compileTastyInDir("../tests/run", defaultOptions,
65+
val (step1, step2, step3) = compileTastyInDir("../tests/run", defaultOptions,
6566
blacklist = Set(
6667
"Course-2002-13.scala",
6768
"NestedClasses.scala",
@@ -93,7 +94,8 @@ class FromTastyTests extends ParallelTesting {
9394
)
9495
step1.checkCompile() // Compile all files to generate the class files with tasty
9596
step2.checkRuns() // Compile from tasty and run the result
96-
(step1 + step2).delete()
97+
step3.checkCompile() // Decompile from tasty
98+
(step1 + step2 + step3).delete()
9799
}
98100

99101
private implicit class tastyCompilationTuples(tup: (CompilationTest, CompilationTest)) {

compiler/test/dotty/tools/vulpix/ParallelTesting.scala

Lines changed: 132 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import dotc.reporting.diagnostic.MessageContainer
2222
import dotc.interfaces.Diagnostic.ERROR
2323
import dotc.util.DiffUtil
2424
import dotc.{ Driver, Compiler }
25+
import dotc.decompiler
2526

2627
/** A parallel testing suite whose goal is to integrate nicely with JUnit
2728
*
@@ -47,7 +48,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
4748
/** A test source whose files or directory of files is to be compiled
4849
* in a specific way defined by the `Test`
4950
*/
50-
private sealed trait TestSource { self =>
51+
protected sealed trait TestSource { self =>
5152
def name: String
5253
def outDir: JFile
5354
def flags: TestFlags
@@ -133,7 +134,8 @@ trait ParallelTesting extends RunnerOrchestration { self =>
133134
files: Array[JFile],
134135
flags: TestFlags,
135136
outDir: JFile,
136-
fromTasty: Boolean = false
137+
fromTasty: Boolean = false,
138+
decompilation: Boolean = false
137139
) extends TestSource {
138140
def sourceFiles: Array[JFile] = files.filter(isSourceFile)
139141

@@ -215,7 +217,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
215217
private val filteredSources =
216218
if (!testFilter.isDefined) testSources
217219
else testSources.filter {
218-
case JointCompilationSource(_, files, _, _, _) =>
220+
case JointCompilationSource(_, files, _, _, _, _) =>
219221
files.exists(file => file.getAbsolutePath.contains(testFilter.get))
220222
case SeparateCompilationSource(_, dir, _, _) =>
221223
dir.getAbsolutePath.contains(testFilter.get)
@@ -422,6 +424,32 @@ trait ParallelTesting extends RunnerOrchestration { self =>
422424
reporter
423425
}
424426

427+
protected def decompile(flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = {
428+
val decompilationOutput = new JFile(targetDir.getPath)
429+
decompilationOutput.mkdir()
430+
val flags = flags0 and ("-d", decompilationOutput.getAbsolutePath) and "-decompile"
431+
432+
def hasTastyFileToClassName(f: JFile): String =
433+
targetDir.toPath.relativize(f.toPath).toString.dropRight(".hasTasty".length).replace('/', '.')
434+
val classes = flattenFiles(targetDir).filter(isHasTastyFile).map(hasTastyFileToClassName)
435+
436+
val reporter =
437+
TestReporter.reporter(realStdout, logLevel =
438+
if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR)
439+
440+
val driver = decompiler.Main
441+
442+
// Compile with a try to catch any StackTrace generated by the compiler:
443+
try {
444+
driver.process(flags.all ++ classes, reporter = reporter)
445+
}
446+
catch {
447+
case NonFatal(ex) => reporter.logStackTrace(ex)
448+
}
449+
450+
reporter
451+
}
452+
425453
private[ParallelTesting] def executeTestSuite(): this.type = {
426454
assert(_testSourcesCompleted == 0, "not allowed to re-use a `CompileRun`")
427455

@@ -474,9 +502,52 @@ trait ParallelTesting extends RunnerOrchestration { self =>
474502
protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable {
475503
def checkTestSource(): Unit = tryCompile(testSource) {
476504
testSource match {
477-
case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty) =>
505+
case testSource @ JointCompilationSource(name, files, flags, outDir, fromTasty, decompilation) =>
478506
val reporter =
479-
if (fromTasty) compileFromTasty(flags, false, outDir)
507+
if (decompilation) {
508+
val rep = decompile(flags, false, outDir)
509+
510+
val checkFileOpt = files.flatMap { file =>
511+
if (file.isDirectory) Nil
512+
else {
513+
val fname = file.getAbsolutePath.reverse.dropWhile(_ != '.').reverse + "decompiled"
514+
val checkFile = new JFile(fname)
515+
if (checkFile.exists) List(checkFile)
516+
else Nil
517+
}
518+
}.headOption
519+
checkFileOpt match {
520+
case Some(checkFile) =>
521+
val stripTrailingWhitespaces = "(.*\\S|)\\s+".r
522+
val output = Source.fromFile(outDir + ".decompiled").getLines().map {line =>
523+
stripTrailingWhitespaces.unapplySeq(line).map(_.head).getOrElse(line)
524+
}.mkString("\n")
525+
.replaceFirst("@scala\\.annotation\\.internal\\.SourceFile\\([^\\)]+\\)( |\\n )", "") // FIXME: should not be printed in the decompiler
526+
527+
checkDiff(output, checkFile, testSource, 0) match {
528+
case Some(diff) =>
529+
println("Expected:")
530+
println(checkFile)
531+
println("Actual output;")
532+
println(output)
533+
println("Diff;")
534+
echo(diff)
535+
addFailureInstruction(diff)
536+
537+
// Print build instructions to file and summary:
538+
val buildInstr = testSource.buildInstructions(0, rep.warningCount)
539+
addFailureInstruction(buildInstr)
540+
541+
// Fail target:
542+
failTestSource(testSource)
543+
case None =>
544+
}
545+
case _ =>
546+
}
547+
548+
rep
549+
}
550+
else if (fromTasty) compileFromTasty(flags, false, outDir)
480551
else compile(testSource.sourceFiles, flags, false, outDir)
481552
registerCompletion(reporter.errorCount)
482553

@@ -526,39 +597,20 @@ trait ParallelTesting extends RunnerOrchestration { self =>
526597
if (Properties.testsNoRun) addNoRunWarning()
527598
else runMain(testSource.runClassPath) match {
528599
case Success(_) if !checkFile.isDefined || !checkFile.get.exists => // success!
529-
case Success(output) => {
530-
val outputLines = output.lines.toArray :+ DiffUtil.EOF
531-
val checkLines: Array[String] = Source.fromFile(checkFile.get).getLines().toArray :+ DiffUtil.EOF
532-
val sourceTitle = testSource.title
533-
534-
def linesMatch =
535-
outputLines
536-
.zip(checkLines)
537-
.forall { case (x, y) => x == y }
538-
539-
if (outputLines.length != checkLines.length || !linesMatch) {
540-
// Print diff to files and summary:
541-
val expectedSize = DiffUtil.EOF.length max checkLines.map(_.length).max
542-
val diff = outputLines.padTo(checkLines.length, "").zip(checkLines.padTo(outputLines.length, "")).map { case (act, exp) =>
543-
DiffUtil.mkColoredLineDiff(exp, act, expectedSize)
544-
}.mkString("\n")
545-
546-
val msg =
547-
s"""|Output from '$sourceTitle' did not match check file.
548-
|Diff (expected on the left, actual right):
549-
|""".stripMargin + diff + "\n"
550-
echo(msg)
551-
addFailureInstruction(msg)
552-
553-
// Print build instructions to file and summary:
554-
val buildInstr = testSource.buildInstructions(0, warnings)
555-
addFailureInstruction(buildInstr)
556-
557-
// Fail target:
558-
failTestSource(testSource)
600+
case Success(output) =>
601+
checkDiff(output, checkFile.get, testSource, warnings) match {
602+
case Some(msg) =>
603+
echo(msg)
604+
addFailureInstruction(msg)
605+
606+
// Print build instructions to file and summary:
607+
val buildInstr = testSource.buildInstructions(0, warnings)
608+
addFailureInstruction(buildInstr)
609+
610+
// Fail target:
611+
failTestSource(testSource)
612+
case None =>
559613
}
560-
}
561-
562614
case Failure(output) =>
563615
echo(s"Test '${testSource.title}' failed with output:")
564616
echo(output)
@@ -573,7 +625,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
573625
protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable {
574626
def checkTestSource(): Unit = tryCompile(testSource) {
575627
val (compilerCrashed, errorCount, warningCount, verifier: Function0[Unit]) = testSource match {
576-
case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty) =>
628+
case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty, decompilation) =>
577629
val checkFile = files.flatMap { file =>
578630
if (file.isDirectory) Nil
579631
else {
@@ -682,7 +734,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
682734
}
683735

684736
val (compilerCrashed, expectedErrors, actualErrors, hasMissingAnnotations, errorMap) = testSource match {
685-
case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty) =>
737+
case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty, decompilation) =>
686738
val sourceFiles = testSource.sourceFiles
687739
val (errorMap, expectedErrors) = getErrorMapAndExpectedCount(sourceFiles)
688740
val reporter = compile(sourceFiles, flags, true, outDir)
@@ -736,6 +788,31 @@ trait ParallelTesting extends RunnerOrchestration { self =>
736788
}
737789
}
738790

791+
private def checkDiff(output: String, checkFile: JFile, testSource: TestSource, warnings: Int): Option[String] = {
792+
val outputLines = output.lines.toArray :+ DiffUtil.EOF
793+
val checkLines: Array[String] = Source.fromFile(checkFile).getLines().toArray :+ DiffUtil.EOF
794+
val sourceTitle = testSource.title
795+
796+
def linesMatch =
797+
outputLines
798+
.zip(checkLines)
799+
.forall { case (x, y) => x == y }
800+
801+
if (outputLines.length != checkLines.length || !linesMatch) {
802+
// Print diff to files and summary:
803+
val expectedSize = DiffUtil.EOF.length max checkLines.map(_.length).max
804+
val diff = outputLines.padTo(checkLines.length, "").zip(checkLines.padTo(outputLines.length, "")).map { case (act, exp) =>
805+
DiffUtil.mkColoredLineDiff(exp, act, expectedSize)
806+
}.mkString("\n")
807+
808+
val msg =
809+
s"""|Output from '$sourceTitle' did not match check file.
810+
|Diff (expected on the left, actual right):
811+
|""".stripMargin + diff + "\n"
812+
Some(msg)
813+
} else None
814+
}
815+
739816
/** The `CompilationTest` is the main interface to `ParallelTesting`, it
740817
* can be instantiated via one of the following methods:
741818
*
@@ -967,7 +1044,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
9671044
*/
9681045
def copyToTarget(): CompilationTest = new CompilationTest (
9691046
targets.map {
970-
case target @ JointCompilationSource(_, files, _, outDir, _) =>
1047+
case target @ JointCompilationSource(_, files, _, outDir, _, _) =>
9711048
target.copy(files = files.map(copyToDir(outDir,_)))
9721049
case target @ SeparateCompilationSource(_, dir, _, outDir) =>
9731050
target.copy(dir = copyToDir(outDir, dir))
@@ -1083,34 +1160,6 @@ trait ParallelTesting extends RunnerOrchestration { self =>
10831160
new CompilationTest(target)
10841161
}
10851162

1086-
/** Compiles a single file from the string path `f` using the supplied flags
1087-
*
1088-
* Tests in the first part of the tuple must be executed before the second.
1089-
* Both testsRequires explicit delete().
1090-
*/
1091-
def compileTasty(f: String, flags: TestFlags)(implicit testGroup: TestGroup): (CompilationTest, CompilationTest) = {
1092-
val sourceFile = new JFile(f)
1093-
val parent = sourceFile.getParentFile
1094-
val outDir =
1095-
defaultOutputDir + testGroup + "/" +
1096-
sourceFile.getName.substring(0, sourceFile.getName.lastIndexOf('.')) + "/"
1097-
1098-
require(
1099-
sourceFile.exists && !sourceFile.isDirectory &&
1100-
(parent ne null) && parent.exists && parent.isDirectory,
1101-
s"Source file: $f, didn't exist"
1102-
)
1103-
val tastySource = createOutputDirsForFile(sourceFile, parent, outDir)
1104-
val target = JointCompilationSource(
1105-
testGroup.name,
1106-
Array(sourceFile),
1107-
flags.withClasspath(tastySource.getPath) and "-from-tasty",
1108-
tastySource,
1109-
fromTasty = true
1110-
)
1111-
(compileFile(f, flags).keepOutput, new CompilationTest(target).keepOutput)
1112-
}
1113-
11141163
/** Compiles a directory `f` using the supplied `flags`. This method does
11151164
* deep compilation, that is - it compiles all files and subdirectories
11161165
* contained within the directory `f`.
@@ -1216,7 +1265,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
12161265
* Both testsRequires explicit delete().
12171266
*/
12181267
def compileTastyInDir(f: String, flags0: TestFlags, blacklist: Set[String] = Set.empty)(
1219-
implicit testGroup: TestGroup): (CompilationTest, CompilationTest) = {
1268+
implicit testGroup: TestGroup): (CompilationTest, CompilationTest, CompilationTest) = {
12201269
val outDir = defaultOutputDir + testGroup + "/"
12211270
val flags = flags0 and "-Yretain-trees"
12221271
val sourceDir = new JFile(f)
@@ -1231,9 +1280,22 @@ trait ParallelTesting extends RunnerOrchestration { self =>
12311280
}
12321281
// TODO add SeparateCompilationSource from tasty?
12331282

1283+
val targets2 =
1284+
files
1285+
.filter(f => dotty.tools.io.File(f.toPath).changeExtension("decompiled").exists)
1286+
.map { f =>
1287+
val classpath = createOutputDirsForFile(f, sourceDir, outDir)
1288+
JointCompilationSource(testGroup.name, Array(f), flags.withClasspath(classpath.getPath), classpath, decompilation = true)
1289+
}
1290+
12341291
// Create a CompilationTest and let the user decide whether to execute a pos or a neg test
12351292
val generateClassFiles = compileFilesInDir(f, flags0, blacklist)
1236-
(generateClassFiles.keepOutput, new CompilationTest(targets).keepOutput)
1293+
1294+
(
1295+
generateClassFiles.keepOutput,
1296+
new CompilationTest(targets).keepOutput,
1297+
new CompilationTest(targets2).keepOutput
1298+
)
12371299
}
12381300

12391301

0 commit comments

Comments
 (0)