Skip to content

Commit ed91a48

Browse files
committed
Create test infrastructure for plugins
1 parent c49c2ed commit ed91a48

File tree

10 files changed

+213
-27
lines changed

10 files changed

+213
-27
lines changed

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

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

316+
@Test def testPlugins: Unit = {
317+
val xmlFile = "scalac-plugin.xml"
318+
319+
// 1. hack with absolute path for -Xplugin
320+
// 2. copy `xmlFile` to destination
321+
def compileFilesInDir(dir: String): CompilationTest = {
322+
val outDir = defaultOutputDir + "testPlugins/"
323+
val sourceDir = new JFile(dir)
324+
325+
val dirs = sourceDir.listFiles.foldLeft(List.empty[JFile]) { case (dirs, f) =>
326+
if (f.isDirectory) f :: dirs else dirs
327+
}
328+
329+
val targets = dirs.map { dir =>
330+
val compileDir = createOutputDirsForDir(dir, sourceDir, outDir)
331+
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
332+
Files.copy(dir.toPath.resolve(xmlFile), compileDir.toPath.resolve(xmlFile), REPLACE_EXISTING)
333+
val flags = TestFlags(classPath, noCheckOptions) and ("-Xplugin:" + compileDir.getAbsolutePath)
334+
SeparateCompilationSource("testPlugins", dir, flags, compileDir)
335+
}
336+
337+
new CompilationTest(targets)
338+
}
339+
340+
compileFilesInDir("../tests/plugins/neg").checkExpectedErrors()
341+
compileFilesInDir("../tests/plugins/pos").checkCompile(checkCompileOutput = true)
342+
}
343+
316344
private val (compilerSources, backendSources, backendJvmSources) = {
317345
val compilerDir = Paths.get("../compiler/src")
318346
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: 68 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,
@@ -143,7 +142,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
143142
/** A test source whose files will be compiled separately according to their
144143
* suffix `_X`
145144
*/
146-
private final case class SeparateCompilationSource(
145+
final case class SeparateCompilationSource(
147146
name: String,
148147
dir: JFile,
149148
flags: TestFlags,
@@ -176,7 +175,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
176175
/** Each `Test` takes the `testSources` and performs the compilation and assertions
177176
* according to the implementing class "neg", "run" or "pos".
178177
*/
179-
private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit val summaryReport: SummaryReporting) { test =>
178+
private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit val summaryReport: SummaryReporting) { test =>
180179

181180
import summaryReport._
182181

@@ -352,9 +351,12 @@ trait ParallelTesting extends RunnerOrchestration { self =>
352351
else None
353352
} else None
354353

354+
val logLevel = if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR
355355
val reporter =
356-
TestReporter.reporter(realStdout, logLevel =
357-
if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR)
356+
if (checkCompileOutput)
357+
TestReporter.storedReporter(realStdout, logLevel = logLevel)
358+
else
359+
TestReporter.reporter(realStdout, logLevel = logLevel)
358360

359361
val driver =
360362
if (times == 1) new Driver
@@ -463,10 +465,28 @@ trait ParallelTesting extends RunnerOrchestration { self =>
463465
private def flattenFiles(f: JFile): Array[JFile] =
464466
if (f.isDirectory) f.listFiles.flatMap(flattenFiles)
465467
else Array(f)
468+
469+
protected def verifyCompileOutput(source: TestSource, checkFile: JFile, reporter: StoredTestReporter): Unit = {
470+
reporter.writer.flush()
471+
val checkLines = Source.fromFile(checkFile).getLines().mkString("\n")
472+
val outputLines = reporter.writer.toString.trim.replaceAll("\\s+\n", "\n")
473+
474+
if (outputLines != checkLines) {
475+
val msg = s"Output from '${source.title}' did not match check file '${checkFile.getName}'."
476+
println("===============================")
477+
println("expected: \n" + checkLines)
478+
println("actual: \n" + outputLines)
479+
println("===============================")
480+
481+
echo(msg)
482+
addFailureInstruction(msg)
483+
failTestSource(source)
484+
}
485+
}
466486
}
467487

468-
private final class PosTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting)
469-
extends Test(testSources, times, threadLimit, suppressAllOutput) {
488+
private final class PosTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting)
489+
extends Test(testSources, times, threadLimit, suppressAllOutput, checkCompileOutput) {
470490
protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable {
471491
def checkTestSource(): Unit = tryCompile(testSource) {
472492
testSource match {
@@ -499,6 +519,15 @@ trait ParallelTesting extends RunnerOrchestration { self =>
499519
reporters.foreach(logReporterContents)
500520
logBuildInstructions(reporters.head, testSource, errorCount, warningCount)
501521
}
522+
523+
// verify compilation check file
524+
(1 to testSource.compilationGroups.length).foreach { index =>
525+
val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + "/" + index + ".check")
526+
527+
if (checkFile.exists && checkCompileOutput)
528+
verifyCompileOutput(testSource, checkFile, reporters(index).asInstanceOf[StoredTestReporter])
529+
}
530+
}
502531
}
503532
}
504533
}
@@ -622,8 +651,8 @@ trait ParallelTesting extends RunnerOrchestration { self =>
622651
}
623652
}
624653

625-
private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting)
626-
extends Test(testSources, times, threadLimit, suppressAllOutput) {
654+
private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting)
655+
extends Test(testSources, times, threadLimit, suppressAllOutput, checkCompileOutput) {
627656
protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable {
628657
def checkTestSource(): Unit = tryCompile(testSource) {
629658
// In neg-tests we allow two types of error annotations,
@@ -700,6 +729,14 @@ trait ParallelTesting extends RunnerOrchestration { self =>
700729
if (actualErrors > 0)
701730
reporters.foreach(logReporterContents)
702731

732+
// Compilation check file: for testing plugins
733+
(1 to testSource.compilationGroups.length).foreach { index =>
734+
val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + "/" + index + ".check")
735+
736+
if (checkFile.exists && checkCompileOutput)
737+
verifyCompileOutput(testSource, checkFile, reporters(index).asInstanceOf[StoredTestReporter])
738+
}
739+
703740
(compilerCrashed, expectedErrors, actualErrors, () => getMissingExpectedErrors(errorMap, errors), errorMap)
704741
}
705742
}
@@ -847,10 +884,10 @@ trait ParallelTesting extends RunnerOrchestration { self =>
847884
) {
848885
import org.junit.Assert.fail
849886

850-
private[ParallelTesting] def this(target: TestSource) =
887+
def this(target: TestSource) =
851888
this(List(target), 1, true, None, false, false)
852889

853-
private[ParallelTesting] def this(targets: List[TestSource]) =
890+
def this(targets: List[TestSource]) =
854891
this(targets, 1, true, None, false, false)
855892

856893
/** Compose test targets from `this` with `other`
@@ -879,8 +916,10 @@ trait ParallelTesting extends RunnerOrchestration { self =>
879916
* compilation without generating errors and that they do not crash the
880917
* compiler
881918
*/
882-
def checkCompile()(implicit summaryReport: SummaryReporting): this.type = {
883-
val test = new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite()
919+
def checkCompile(checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting): this.type = {
920+
val test = new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput, checkCompileOutput).executeTestSuite()
921+
922+
cleanup()
884923

885924
if (!shouldFail && test.didFail) {
886925
fail(s"Expected no errors when compiling, failed for the following reason(s):\n${ reasonsForFailure(test) }")
@@ -889,15 +928,17 @@ trait ParallelTesting extends RunnerOrchestration { self =>
889928
fail("Pos test should have failed, but didn't")
890929
}
891930

892-
cleanup()
931+
this
893932
}
894933

895934
/** Creates a "neg" test run, which makes sure that each test generates the
896935
* correct amount of errors at the correct positions. It also makes sure
897936
* that none of these tests crash the compiler
898937
*/
899-
def checkExpectedErrors()(implicit summaryReport: SummaryReporting): this.type = {
900-
val test = new NegTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite()
938+
def checkExpectedErrors(checkCompileOutput : Boolean = false)(implicit summaryReport: SummaryReporting): this.type = {
939+
val test = new NegTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput, checkCompileOutput).executeTestSuite()
940+
941+
cleanup()
901942

902943
if (!shouldFail && test.didFail) {
903944
fail(s"Neg test shouldn't have failed, but did. Reasons:\n${ reasonsForFailure(test) }")
@@ -906,7 +947,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
906947
fail("Neg test should have failed, but did not")
907948
}
908949

909-
cleanup()
950+
this
910951
}
911952

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

961+
cleanup()
962+
920963
if (!shouldFail && test.didFail) {
921964
fail(s"Run test failed, but should not, reasons:\n${ reasonsForFailure(test) }")
922965
}
923966
else if (shouldFail && !test.didFail) {
924967
fail("Run test should have failed, but did not")
925968
}
926969

927-
cleanup()
970+
this
928971
}
929972

930973
/** Deletes output directories and files */
@@ -1027,7 +1070,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
10271070
}
10281071

10291072
/** Create out directory for directory `d` */
1030-
private def createOutputDirsForDir(d: JFile, sourceDir: JFile, outDir: String): JFile = {
1073+
def createOutputDirsForDir(d: JFile, sourceDir: JFile, outDir: String): JFile = {
10311074
val targetDir = new JFile(outDir + s"${sourceDir.getName}/${d.getName}")
10321075
targetDir.mkdirs()
10331076
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)