Skip to content

Commit 3f5900c

Browse files
committed
Create test infrastructure for plugins
1 parent ea05a3b commit 3f5900c

File tree

10 files changed

+214
-27
lines changed

10 files changed

+214
-27
lines changed

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,34 @@ class CompilationTests extends ParallelTesting {
311311
compileShallowFilesInDir("../tests/neg", defaultOptimised, outputDir).checkExpectedErrors()
312312
}
313313

314+
@Test def testPlugins: Unit = {
315+
val xmlFile = "scalac-plugin.xml"
316+
317+
// 1. hack with absolute path for -Xplugin
318+
// 2. copy `xmlFile` to destination
319+
def compileFilesInDir(dir: String): CompilationTest = {
320+
val outDir = defaultOutputDir + "testPlugins/"
321+
val sourceDir = new JFile(dir)
322+
323+
val dirs = sourceDir.listFiles.foldLeft(List.empty[JFile]) { case (dirs, f) =>
324+
if (f.isDirectory) f :: dirs else dirs
325+
}
326+
327+
val targets = dirs.map { dir =>
328+
val compileDir = createOutputDirsForDir(dir, sourceDir, outDir)
329+
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
330+
Files.copy(dir.toPath.resolve(xmlFile), compileDir.toPath.resolve(xmlFile), REPLACE_EXISTING)
331+
val flags = TestFlags(classPath, noCheckOptions) and ("-Xplugin:" + compileDir.getAbsolutePath)
332+
SeparateCompilationSource("testPlugins", dir, flags, compileDir)
333+
}
334+
335+
new CompilationTest(targets)
336+
}
337+
338+
compileFilesInDir("../tests/plugins/neg").checkExpectedErrors()
339+
compileFilesInDir("../tests/plugins/pos").checkCompile(checkCompileOutput = true)
340+
}
341+
314342
private val (compilerSources, backendSources, backendJvmSources) = {
315343
val compilerDir = Paths.get("../compiler/src")
316344
val compilerSources0 = sources(Files.walk(compilerDir))

compiler/test/dotty/tools/dotc/reporting/TestReporter.scala

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ package dotty.tools
22
package dotc
33
package reporting
44

5-
import java.io.{ PrintStream, PrintWriter, File => JFile, FileOutputStream }
5+
import java.io.{ FileOutputStream, PrintStream, PrintWriter, StringWriter, File => JFile }
66
import java.text.SimpleDateFormat
77
import java.util.Date
88

99
import scala.collection.mutable
10-
1110
import util.SourcePosition
1211
import core.Contexts._
1312
import Reporter._
@@ -79,6 +78,23 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M
7978
}
8079
}
8180

81+
class StoredTestReporter (val writer: StringWriter, val superWriter: PrintWriter, filePrintln: String => Unit, logLevel: Int)
82+
extends TestReporter(superWriter, filePrintln, logLevel) {
83+
override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = {
84+
super.doReport(m)
85+
86+
val msg =
87+
if (m.pos.exists)
88+
"[" + diagnosticLevel(m) + "] Line " + (m.pos.line + 1) + ": " + m.contained().msg
89+
else
90+
"[" + diagnosticLevel(m) + "] " + m.contained().msg
91+
92+
writer.write(msg + "\n")
93+
}
94+
95+
override def summary: String = ""
96+
}
97+
8298
object TestReporter {
8399
private[this] var outFile: JFile = _
84100
private[this] var logWriter: PrintWriter = _
@@ -115,6 +131,9 @@ object TestReporter {
115131
def reporter(ps: PrintStream, logLevel: Int): TestReporter =
116132
new TestReporter(new PrintWriter(ps, true), logPrintln, logLevel)
117133

134+
def storedReporter(ps: PrintStream, logLevel: Int): TestReporter =
135+
new StoredTestReporter(new StringWriter(), new PrintWriter(ps, true), logPrintln, logLevel)
136+
118137
def simplifiedReporter(writer: PrintWriter): TestReporter = {
119138
val rep = new TestReporter(writer, logPrintln, WARNING) {
120139
/** Prints the message with the given position indication in a simplified manner */

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

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,21 @@ import java.io.{ File => JFile }
66
import java.text.SimpleDateFormat
77
import java.util.HashMap
88
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
9-
import java.nio.file.{ Files, Path, Paths, NoSuchFileException }
10-
import java.util.concurrent.{ Executors => JExecutors, TimeUnit, TimeoutException }
9+
import java.nio.file.{ Files, NoSuchFileException, Path, Paths }
10+
import java.util.concurrent.{ TimeUnit, TimeoutException, Executors => JExecutors }
1111

1212
import scala.io.Source
1313
import scala.util.control.NonFatal
1414
import scala.util.Try
1515
import scala.collection.mutable
1616
import scala.util.matching.Regex
1717
import scala.util.Random
18-
1918
import dotc.core.Contexts._
20-
import dotc.reporting.{ Reporter, TestReporter }
19+
import dotc.reporting.{ Reporter, StoredTestReporter, TestReporter }
2120
import dotc.reporting.diagnostic.MessageContainer
2221
import dotc.interfaces.Diagnostic.ERROR
2322
import dotc.util.DiffUtil
24-
import dotc.{ Driver, Compiler }
23+
import dotc.{ Compiler, Driver }
2524

2625
/** A parallel testing suite whose goal is to integrate nicely with JUnit
2726
*
@@ -47,7 +46,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
4746
/** A test source whose files or directory of files is to be compiled
4847
* in a specific way defined by the `Test`
4948
*/
50-
private sealed trait TestSource { self =>
49+
sealed trait TestSource { self =>
5150
def name: String
5251
def outDir: JFile
5352
def flags: TestFlags
@@ -128,7 +127,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
128127
/** A group of files that may all be compiled together, with the same flags
129128
* and output directory
130129
*/
131-
private final case class JointCompilationSource(
130+
final case class JointCompilationSource(
132131
name: String,
133132
files: Array[JFile],
134133
flags: TestFlags,
@@ -142,7 +141,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
142141
/** A test source whose files will be compiled separately according to their
143142
* suffix `_X`
144143
*/
145-
private final case class SeparateCompilationSource(
144+
final case class SeparateCompilationSource(
146145
name: String,
147146
dir: JFile,
148147
flags: TestFlags,
@@ -175,7 +174,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
175174
/** Each `Test` takes the `testSources` and performs the compilation and assertions
176175
* according to the implementing class "neg", "run" or "pos".
177176
*/
178-
private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit val summaryReport: SummaryReporting) { test =>
177+
private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit val summaryReport: SummaryReporting) { test =>
179178

180179
import summaryReport._
181180

@@ -360,9 +359,12 @@ trait ParallelTesting extends RunnerOrchestration { self =>
360359
else None
361360
} else None
362361

362+
val logLevel = if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR
363363
val reporter =
364-
TestReporter.reporter(realStdout, logLevel =
365-
if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR)
364+
if (checkCompileOutput)
365+
TestReporter.storedReporter(realStdout, logLevel = logLevel)
366+
else
367+
TestReporter.reporter(realStdout, logLevel = logLevel)
366368

367369
val driver =
368370
if (times == 1) new Driver
@@ -440,10 +442,28 @@ trait ParallelTesting extends RunnerOrchestration { self =>
440442

441443
this
442444
}
445+
446+
protected def verifyCompileOutput(source: TestSource, checkFile: JFile, reporter: StoredTestReporter): Unit = {
447+
reporter.writer.flush()
448+
val checkLines = Source.fromFile(checkFile).getLines().mkString("\n")
449+
val outputLines = reporter.writer.toString.trim.replaceAll("\\s+\n", "\n")
450+
451+
if (outputLines != checkLines) {
452+
val msg = s"Output from '${source.title}' did not match check file '${checkFile.getName}'."
453+
println("===============================")
454+
println("expected: \n" + checkLines)
455+
println("actual: \n" + outputLines)
456+
println("===============================")
457+
458+
echo(msg)
459+
addFailureInstruction(msg)
460+
failTestSource(source)
461+
}
462+
}
443463
}
444464

445-
private final class PosTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting)
446-
extends Test(testSources, times, threadLimit, suppressAllOutput) {
465+
private final class PosTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting)
466+
extends Test(testSources, times, threadLimit, suppressAllOutput, checkCompileOutput) {
447467
protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable {
448468
def checkTestSource(): Unit = tryCompile(testSource) {
449469
testSource match {
@@ -475,6 +495,14 @@ trait ParallelTesting extends RunnerOrchestration { self =>
475495
reporters.foreach(logReporterContents)
476496
logBuildInstructions(reporters.head, testSource, errorCount, warningCount)
477497
}
498+
499+
// verify compilation check file
500+
(1 to testSource.compilationGroups.length).foreach { index =>
501+
val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + "/" + index + ".check")
502+
503+
if (checkFile.exists && checkCompileOutput)
504+
verifyCompileOutput(testSource, checkFile, reporters(index).asInstanceOf[StoredTestReporter])
505+
}
478506
}
479507
}
480508
}
@@ -599,8 +627,8 @@ trait ParallelTesting extends RunnerOrchestration { self =>
599627
}
600628
}
601629

602-
private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting)
603-
extends Test(testSources, times, threadLimit, suppressAllOutput) {
630+
private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting)
631+
extends Test(testSources, times, threadLimit, suppressAllOutput, checkCompileOutput) {
604632
protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable {
605633
def checkTestSource(): Unit = tryCompile(testSource) {
606634
// In neg-tests we allow two types of error annotations,
@@ -678,6 +706,14 @@ trait ParallelTesting extends RunnerOrchestration { self =>
678706
if (actualErrors > 0)
679707
reporters.foreach(logReporterContents)
680708

709+
// Compilation check file: for testing plugins
710+
(1 to testSource.compilationGroups.length).foreach { index =>
711+
val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + "/" + index + ".check")
712+
713+
if (checkFile.exists && checkCompileOutput)
714+
verifyCompileOutput(testSource, checkFile, reporters(index).asInstanceOf[StoredTestReporter])
715+
}
716+
681717
(compilerCrashed, expectedErrors, actualErrors, () => getMissingExpectedErrors(errorMap, errors), errorMap)
682718
}
683719
}
@@ -825,10 +861,10 @@ trait ParallelTesting extends RunnerOrchestration { self =>
825861
) {
826862
import org.junit.Assert.fail
827863

828-
private[ParallelTesting] def this(target: TestSource) =
864+
def this(target: TestSource) =
829865
this(List(target), 1, true, None, false, false)
830866

831-
private[ParallelTesting] def this(targets: List[TestSource]) =
867+
def this(targets: List[TestSource]) =
832868
this(targets, 1, true, None, false, false)
833869

834870
/** Compose test targets from `this` with `other`
@@ -857,8 +893,10 @@ trait ParallelTesting extends RunnerOrchestration { self =>
857893
* compilation without generating errors and that they do not crash the
858894
* compiler
859895
*/
860-
def checkCompile()(implicit summaryReport: SummaryReporting): this.type = {
861-
val test = new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite()
896+
def checkCompile(checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting): this.type = {
897+
val test = new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput, checkCompileOutput).executeTestSuite()
898+
899+
cleanup()
862900

863901
if (!shouldFail && test.didFail) {
864902
fail(s"Expected no errors when compiling, failed for the following reason(s):\n${ reasonsForFailure(test) }")
@@ -867,15 +905,17 @@ trait ParallelTesting extends RunnerOrchestration { self =>
867905
fail("Pos test should have failed, but didn't")
868906
}
869907

870-
cleanup()
908+
this
871909
}
872910

873911
/** Creates a "neg" test run, which makes sure that each test generates the
874912
* correct amount of errors at the correct positions. It also makes sure
875913
* that none of these tests crash the compiler
876914
*/
877-
def checkExpectedErrors()(implicit summaryReport: SummaryReporting): this.type = {
878-
val test = new NegTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite()
915+
def checkExpectedErrors(checkCompileOutput : Boolean = false)(implicit summaryReport: SummaryReporting): this.type = {
916+
val test = new NegTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput, checkCompileOutput).executeTestSuite()
917+
918+
cleanup()
879919

880920
if (!shouldFail && test.didFail) {
881921
fail(s"Neg test shouldn't have failed, but did. Reasons:\n${ reasonsForFailure(test) }")
@@ -884,7 +924,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
884924
fail("Neg test should have failed, but did not")
885925
}
886926

887-
cleanup()
927+
this
888928
}
889929

890930
/** Creates a "run" test run, which is a superset of "pos". In addition to
@@ -895,14 +935,16 @@ trait ParallelTesting extends RunnerOrchestration { self =>
895935
def checkRuns()(implicit summaryReport: SummaryReporting): this.type = {
896936
val test = new RunTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite()
897937

938+
cleanup()
939+
898940
if (!shouldFail && test.didFail) {
899941
fail(s"Run test failed, but should not, reasons:\n${ reasonsForFailure(test) }")
900942
}
901943
else if (shouldFail && !test.didFail) {
902944
fail("Run test should have failed, but did not")
903945
}
904946

905-
cleanup()
947+
this
906948
}
907949

908950
/** Deletes output directories and files */
@@ -1005,7 +1047,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
10051047
}
10061048

10071049
/** Create out directory for directory `d` */
1008-
private def createOutputDirsForDir(d: JFile, sourceDir: JFile, outDir: String): JFile = {
1050+
def createOutputDirsForDir(d: JFile, sourceDir: JFile, outDir: String): JFile = {
10091051
val targetDir = new JFile(outDir + s"${sourceDir.getName}/${d.getName}")
10101052
targetDir.mkdirs()
10111053
targetDir
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class Test {
2+
val y = 5 / 0 // error
3+
100 + 6 / 0 // error
4+
6L / 0L // error
5+
val z = 7 / 0.0 // error
6+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import dotty.tools.dotc._
2+
import core._
3+
import Contexts.Context
4+
import plugins.Plugin
5+
import Phases.Phase
6+
import ast.tpd
7+
import transform.MegaPhase.MiniPhase
8+
import Decorators._
9+
import Symbols.Symbol
10+
import Constants.Constant
11+
12+
class DivideZero extends MiniPhase with Plugin {
13+
val name: String = "divideZero"
14+
override val description: String = "divide zero check"
15+
16+
val phaseName = name
17+
18+
override def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = {
19+
val (before, after) = phases.span(ps => !ps.exists(_.phaseName == "pickler"))
20+
before ++ (List(this) :: after)
21+
}
22+
23+
private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = {
24+
def test(tpe: String): Boolean =
25+
(sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name.show == "/"
26+
27+
test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.FLoat") || test("scala.Double")
28+
}
29+
30+
override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = tree match {
31+
case tpd.Apply(fun, tpd.Literal(Constants.Constant(v)) :: Nil) if isNumericDivide(fun.symbol) && v == 0 =>
32+
ctx.error("divide by zero", tree.pos)
33+
tree
34+
case _ =>
35+
tree
36+
}
37+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<plugin>
2+
<name>divideZero</name>
3+
<classname>DivideZero</classname>
4+
</plugin>

tests/plugins/pos/divideZero/2.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[Warning] Line 2: divide by zero
2+
[Warning] Line 3: divide by zero
3+
[Warning] Line 4: divide by zero
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class Test {
2+
100 + 6 / 0
3+
6L / 0L
4+
println(3 + 7 / 0.0)
5+
}

0 commit comments

Comments
 (0)