Skip to content

Commit 2a65edf

Browse files
committed
Add decompiler tests
1 parent 2166012 commit 2a65edf

File tree

10 files changed

+239
-81
lines changed

10 files changed

+239
-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: 126 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)
@@ -419,6 +421,32 @@ trait ParallelTesting extends RunnerOrchestration { self =>
419421
reporter
420422
}
421423

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

@@ -471,9 +499,46 @@ trait ParallelTesting extends RunnerOrchestration { self =>
471499
protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable {
472500
def checkTestSource(): Unit = tryCompile(testSource) {
473501
testSource match {
474-
case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty) =>
502+
case testSource @ JointCompilationSource(name, files, flags, outDir, fromTasty, decompilation) =>
475503
val reporter =
476-
if (fromTasty) compileFromTasty(flags, false, outDir)
504+
if (decompilation) {
505+
val rep = decompile(flags, false, outDir)
506+
507+
val checkFileOpt = files.flatMap { file =>
508+
if (file.isDirectory) Nil
509+
else {
510+
val fname = file.getAbsolutePath.reverse.dropWhile(_ != '.').reverse + "decompiled"
511+
val checkFile = new JFile(fname)
512+
if (checkFile.exists) List(checkFile)
513+
else Nil
514+
}
515+
}.headOption
516+
checkFileOpt match {
517+
case Some(checkFile) =>
518+
val stripTrailingWhitespaces = "(.*\\S|)\\s+".r
519+
val output = Source.fromFile(outDir + ".decompiled").getLines().map {line =>
520+
stripTrailingWhitespaces.unapplySeq(line).map(_.head).getOrElse(line)
521+
}.mkString("\n")
522+
523+
checkDiff(output, checkFile, testSource, 0) match {
524+
case Some(diff) =>
525+
echo(diff)
526+
addFailureInstruction(diff)
527+
528+
// Print build instructions to file and summary:
529+
val buildInstr = testSource.buildInstructions(0, rep.warningCount)
530+
addFailureInstruction(buildInstr)
531+
532+
// Fail target:
533+
failTestSource(testSource)
534+
case None =>
535+
}
536+
case _ =>
537+
}
538+
539+
rep
540+
}
541+
else if (fromTasty) compileFromTasty(flags, false, outDir)
477542
else compile(testSource.sourceFiles, flags, false, outDir)
478543
registerCompletion(reporter.errorCount)
479544

@@ -523,39 +588,20 @@ trait ParallelTesting extends RunnerOrchestration { self =>
523588
if (Properties.testsNoRun) addNoRunWarning()
524589
else runMain(testSource.runClassPath) match {
525590
case Success(_) if !checkFile.isDefined || !checkFile.get.exists => // success!
526-
case Success(output) => {
527-
val outputLines = output.lines.toArray :+ DiffUtil.EOF
528-
val checkLines: Array[String] = Source.fromFile(checkFile.get).getLines().toArray :+ DiffUtil.EOF
529-
val sourceTitle = testSource.title
530-
531-
def linesMatch =
532-
outputLines
533-
.zip(checkLines)
534-
.forall { case (x, y) => x == y }
535-
536-
if (outputLines.length != checkLines.length || !linesMatch) {
537-
// Print diff to files and summary:
538-
val expectedSize = DiffUtil.EOF.length max checkLines.map(_.length).max
539-
val diff = outputLines.padTo(checkLines.length, "").zip(checkLines.padTo(outputLines.length, "")).map { case (act, exp) =>
540-
DiffUtil.mkColoredLineDiff(exp, act, expectedSize)
541-
}.mkString("\n")
542-
543-
val msg =
544-
s"""|Output from '$sourceTitle' did not match check file.
545-
|Diff (expected on the left, actual right):
546-
|""".stripMargin + diff + "\n"
547-
echo(msg)
548-
addFailureInstruction(msg)
549-
550-
// Print build instructions to file and summary:
551-
val buildInstr = testSource.buildInstructions(0, warnings)
552-
addFailureInstruction(buildInstr)
553-
554-
// Fail target:
555-
failTestSource(testSource)
591+
case Success(output) =>
592+
checkDiff(output, checkFile.get, testSource, warnings) match {
593+
case Some(msg) =>
594+
echo(msg)
595+
addFailureInstruction(msg)
596+
597+
// Print build instructions to file and summary:
598+
val buildInstr = testSource.buildInstructions(0, warnings)
599+
addFailureInstruction(buildInstr)
600+
601+
// Fail target:
602+
failTestSource(testSource)
603+
case None =>
556604
}
557-
}
558-
559605
case Failure(output) =>
560606
echo(s"Test '${testSource.title}' failed with output:")
561607
echo(output)
@@ -570,7 +616,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
570616
protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable {
571617
def checkTestSource(): Unit = tryCompile(testSource) {
572618
val (compilerCrashed, errorCount, warningCount, verifier: Function0[Unit]) = testSource match {
573-
case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty) =>
619+
case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty, decompilation) =>
574620
val checkFile = files.flatMap { file =>
575621
if (file.isDirectory) Nil
576622
else {
@@ -679,7 +725,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
679725
}
680726

681727
val (compilerCrashed, expectedErrors, actualErrors, hasMissingAnnotations, errorMap) = testSource match {
682-
case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty) =>
728+
case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty, decompilation) =>
683729
val sourceFiles = testSource.sourceFiles
684730
val (errorMap, expectedErrors) = getErrorMapAndExpectedCount(sourceFiles)
685731
val reporter = compile(sourceFiles, flags, true, outDir)
@@ -733,6 +779,31 @@ trait ParallelTesting extends RunnerOrchestration { self =>
733779
}
734780
}
735781

782+
private def checkDiff(output: String, checkFile: JFile, testSource: TestSource, warnings: Int): Option[String] = {
783+
val outputLines = output.lines.toArray :+ DiffUtil.EOF
784+
val checkLines: Array[String] = Source.fromFile(checkFile).getLines().toArray :+ DiffUtil.EOF
785+
val sourceTitle = testSource.title
786+
787+
def linesMatch =
788+
outputLines
789+
.zip(checkLines)
790+
.forall { case (x, y) => x == y }
791+
792+
if (outputLines.length != checkLines.length || !linesMatch) {
793+
// Print diff to files and summary:
794+
val expectedSize = DiffUtil.EOF.length max checkLines.map(_.length).max
795+
val diff = outputLines.padTo(checkLines.length, "").zip(checkLines.padTo(outputLines.length, "")).map { case (act, exp) =>
796+
DiffUtil.mkColoredLineDiff(exp, act, expectedSize)
797+
}.mkString("\n")
798+
799+
val msg =
800+
s"""|Output from '$sourceTitle' did not match check file.
801+
|Diff (expected on the left, actual right):
802+
|""".stripMargin + diff + "\n"
803+
Some(msg)
804+
} else None
805+
}
806+
736807
/** The `CompilationTest` is the main interface to `ParallelTesting`, it
737808
* can be instantiated via one of the following methods:
738809
*
@@ -964,7 +1035,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
9641035
*/
9651036
def copyToTarget(): CompilationTest = new CompilationTest (
9661037
targets.map {
967-
case target @ JointCompilationSource(_, files, _, outDir, _) =>
1038+
case target @ JointCompilationSource(_, files, _, outDir, _, _) =>
9681039
target.copy(files = files.map(copyToDir(outDir,_)))
9691040
case target @ SeparateCompilationSource(_, dir, _, outDir) =>
9701041
target.copy(dir = copyToDir(outDir, dir))
@@ -1080,34 +1151,6 @@ trait ParallelTesting extends RunnerOrchestration { self =>
10801151
new CompilationTest(target)
10811152
}
10821153

1083-
/** Compiles a single file from the string path `f` using the supplied flags
1084-
*
1085-
* Tests in the first part of the tuple must be executed before the second.
1086-
* Both testsRequires explicit delete().
1087-
*/
1088-
def compileTasty(f: String, flags: TestFlags)(implicit testGroup: TestGroup): (CompilationTest, CompilationTest) = {
1089-
val sourceFile = new JFile(f)
1090-
val parent = sourceFile.getParentFile
1091-
val outDir =
1092-
defaultOutputDir + testGroup + "/" +
1093-
sourceFile.getName.substring(0, sourceFile.getName.lastIndexOf('.')) + "/"
1094-
1095-
require(
1096-
sourceFile.exists && !sourceFile.isDirectory &&
1097-
(parent ne null) && parent.exists && parent.isDirectory,
1098-
s"Source file: $f, didn't exist"
1099-
)
1100-
val tastySource = createOutputDirsForFile(sourceFile, parent, outDir)
1101-
val target = JointCompilationSource(
1102-
testGroup.name,
1103-
Array(sourceFile),
1104-
flags.withClasspath(tastySource.getPath) and "-from-tasty",
1105-
tastySource,
1106-
fromTasty = true
1107-
)
1108-
(compileFile(f, flags).keepOutput, new CompilationTest(target).keepOutput)
1109-
}
1110-
11111154
/** Compiles a directory `f` using the supplied `flags`. This method does
11121155
* deep compilation, that is - it compiles all files and subdirectories
11131156
* contained within the directory `f`.
@@ -1213,7 +1256,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
12131256
* Both testsRequires explicit delete().
12141257
*/
12151258
def compileTastyInDir(f: String, flags0: TestFlags, blacklist: Set[String] = Set.empty)(
1216-
implicit testGroup: TestGroup): (CompilationTest, CompilationTest) = {
1259+
implicit testGroup: TestGroup): (CompilationTest, CompilationTest, CompilationTest) = {
12171260
val outDir = defaultOutputDir + testGroup + "/"
12181261
val flags = flags0 and "-Yretain-trees"
12191262
val sourceDir = new JFile(f)
@@ -1228,9 +1271,22 @@ trait ParallelTesting extends RunnerOrchestration { self =>
12281271
}
12291272
// TODO add SeparateCompilationSource from tasty?
12301273

1274+
val targets2 =
1275+
files
1276+
.filter(f => dotty.tools.io.File(f.toPath).changeExtension("decompiled").exists)
1277+
.map { f =>
1278+
val classpath = createOutputDirsForFile(f, sourceDir, outDir)
1279+
JointCompilationSource(testGroup.name, Array(f), flags.withClasspath(classpath.getPath), classpath, decompilation = true)
1280+
}
1281+
12311282
// Create a CompilationTest and let the user decide whether to execute a pos or a neg test
12321283
val generateClassFiles = compileFilesInDir(f, flags0, blacklist)
1233-
(generateClassFiles.keepOutput, new CompilationTest(targets).keepOutput)
1284+
1285+
(
1286+
generateClassFiles.keepOutput,
1287+
new CompilationTest(targets).keepOutput,
1288+
new CompilationTest(targets2).keepOutput
1289+
)
12341290
}
12351291

12361292

0 commit comments

Comments
 (0)