Skip to content

Commit ea407f1

Browse files
committed
Merge pull request #1106 from dotty-staging/neg-lines
Neg tests check files for // error markers (rebased and updated)
2 parents 46b7fc7 + 70f5d93 commit ea407f1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+279
-144
lines changed

src/dotty/tools/dotc/reporting/Reporter.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ abstract class Reporter {
223223
var warningCount = 0
224224
def hasErrors = errorCount > 0
225225
def hasWarnings = warningCount > 0
226+
private var errors: List[Error] = Nil
227+
def allErrors = errors
226228

227229
/** Have errors been reported by this reporter, or in the
228230
* case where this is a StoreReporter, by an outer reporter?
@@ -238,7 +240,9 @@ abstract class Reporter {
238240
d match {
239241
case d: ConditionalWarning if !d.enablingOption.value => unreportedWarnings(d.enablingOption.name) += 1
240242
case d: Warning => warningCount += 1
241-
case d: Error => errorCount += 1
243+
case d: Error =>
244+
errors = d :: errors
245+
errorCount += 1
242246
case d: Info => // nothing to do here
243247
// match error if d is something else
244248
}

test/test/CompilerTest.scala

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package test
33
import dotty.partest.DPConfig
44
import dotty.tools.dotc.{Main, Bench, Driver}
55
import dotty.tools.dotc.reporting.Reporter
6+
import dotty.tools.dotc.util.SourcePosition
7+
import dotty.tools.dotc.config.CompilerCommand
68
import scala.collection.mutable.ListBuffer
7-
import scala.reflect.io.{ Path, Directory, File => SFile }
9+
import scala.reflect.io.{ Path, Directory, File => SFile, AbstractFile }
810
import scala.tools.partest.nest.{ FileManager, NestUI }
11+
import scala.annotation.tailrec
912
import java.io.{ RandomAccessFile, File => JFile }
1013

1114
import org.junit.Test
@@ -178,13 +181,140 @@ abstract class CompilerTest {
178181

179182
// ========== HELPERS =============
180183

181-
private def compileArgs(args: Array[String], xerrors: Int = 0)(implicit defaultOptions: List[String]): Unit = {
184+
private def compileArgs(args: Array[String], xerrors: Int = 0)
185+
(implicit defaultOptions: List[String]): Unit = {
182186
val allArgs = args ++ defaultOptions
183187
val processor = if (allArgs.exists(_.startsWith("#"))) Bench else Main
184-
val nerrors = processor.process(allArgs).errorCount
188+
val reporter = processor.process(allArgs)
189+
190+
val nerrors = reporter.errorCount
185191
assert(nerrors == xerrors, s"Wrong # of errors. Expected: $xerrors, found: $nerrors")
192+
193+
// NEG TEST
194+
if (xerrors > 0) {
195+
val errorLines = reporter.allErrors.map(_.pos)
196+
// reporter didn't record as many errors as its errorCount says
197+
assert(errorLines.length == nerrors, s"Not enough errors recorded.")
198+
199+
val allFiles = (allArgs filter {
200+
arg => !arg.startsWith("-") && (arg.endsWith(".scala") || arg.endsWith(".java"))
201+
}).toList
202+
val expectedErrorsPerFile = allFiles.map(getErrors(_))
203+
204+
// Some compiler errors have an associated source position. Each error
205+
// needs to correspond to a "// error" marker on that line in the source
206+
// file and vice versa.
207+
// Other compiler errors don't have an associated source position. Their
208+
// number should correspond to the total count of "// nopos-error"
209+
// markers in all files
210+
val (errorsByFile, errorsWithoutPos) = errorLines.groupBy(_.source.file).toList.partition(_._1.toString != "<no source>")
211+
212+
// check errors with source position
213+
val foundErrorsPerFile = errorsByFile.map({ case (fileName, errorList) =>
214+
val posErrorLinesToNr = errorList.groupBy(_.line).toList.map({ case (line, list) => (line, list.length) }).sortBy(_._1)
215+
ErrorsInFile(fileName.toString, 0, posErrorLinesToNr)
216+
})
217+
val expectedErrorsPerFileZeroed = expectedErrorsPerFile.map({
218+
case ErrorsInFile(fileName, _, posErrorLinesToNr) =>
219+
ErrorsInFile(fileName.toString, 0, posErrorLinesToNr)
220+
})
221+
checkErrorsWithPosition(expectedErrorsPerFileZeroed, foundErrorsPerFile)
222+
223+
// check errors without source position
224+
val expectedNoPos = expectedErrorsPerFile.map(_.noposErrorNr).sum
225+
val foundNoPos = errorsWithoutPos.map(_._2.length).sum
226+
assert(foundNoPos == expectedNoPos,
227+
s"Wrong # of errors without source position. Expected (all files): $expectedNoPos, found (compiler): $foundNoPos")
228+
}
186229
}
187230

231+
// ========== NEG TEST HELPERS =============
232+
233+
/** Captures the number of nopos-errors in the given file and the number of
234+
* errors with a position, represented as a tuple of source line and number
235+
* of errors on that line. */
236+
case class ErrorsInFile(fileName: String, noposErrorNr: Int, posErrorLinesToNr: List[(Int, Int)])
237+
238+
/** Extracts the errors expected for the given neg test file. */
239+
def getErrors(fileName: String): ErrorsInFile = {
240+
val content = SFile(fileName).slurp
241+
val (line, rest) = content.span(_ != '\n')
242+
243+
@tailrec
244+
def checkLine(line: String, rest: String, index: Int, noposAcc: Int, posAcc: List[(Int, Int)]): ErrorsInFile = {
245+
val posErrors = "// ?error".r.findAllIn(line).length
246+
val newPosAcc = if (posErrors > 0) (index, posErrors) :: posAcc else posAcc
247+
val newNoPosAcc = noposAcc + "// ?nopos-error".r.findAllIn(line).length
248+
val (newLine, newRest) = rest.span(_ != '\n')
249+
if (newRest.isEmpty)
250+
ErrorsInFile(fileName.toString, newNoPosAcc, newPosAcc.reverse)
251+
else
252+
checkLine(newLine, newRest.tail, index + 1, newNoPosAcc, newPosAcc) // skip leading '\n'
253+
}
254+
255+
checkLine(line, rest.tail, 0, 0, Nil) // skip leading '\n'
256+
}
257+
258+
/** Asserts that the expected and found number of errors correspond, and
259+
* otherwise throws an error with the filename, plus optionally a line
260+
* number if available. */
261+
def errorMsg(fileName: String, lineNumber: Option[Int], exp: Int, found: Int) = {
262+
val i = lineNumber.map({ i => ":" + (i + 1) }).getOrElse("")
263+
assert(found == exp, s"Wrong # of errors for $fileName$i. Expected (file): $exp, found (compiler): $found")
264+
}
265+
266+
/** Compares the expected with the found errors and creates a nice error
267+
* message if they don't agree. */
268+
def checkErrorsWithPosition(expected: List[ErrorsInFile], found: List[ErrorsInFile]): Unit = {
269+
// create nice error messages
270+
expected.diff(found) match {
271+
case Nil => // nothing missing
272+
case ErrorsInFile(fileName, _, expectedLines) :: xs =>
273+
found.find(_.fileName == fileName) match {
274+
case None =>
275+
// expected some errors, but none found for this file
276+
errorMsg(fileName, None, expectedLines.map(_._2).sum, 0)
277+
case Some(ErrorsInFile(_,_,foundLines)) =>
278+
// found wrong number/location of markers for this file
279+
compareLines(fileName, expectedLines, foundLines)
280+
}
281+
}
282+
283+
found.diff(expected) match {
284+
case Nil => // nothing missing
285+
case ErrorsInFile(fileName, _, foundLines) :: xs =>
286+
expected.find(_.fileName == fileName) match {
287+
case None =>
288+
// found some errors, but none expected for this file
289+
errorMsg(fileName, None, 0, foundLines.map(_._2).sum)
290+
case Some(ErrorsInFile(_,_,expectedLines)) =>
291+
// found wrong number/location of markers for this file
292+
compareLines(fileName, expectedLines, foundLines)
293+
}
294+
}
295+
}
296+
297+
/** Gives an error message for one line where the expected number of errors and
298+
* the number of compiler errors differ. */
299+
def compareLines(fileName: String, expectedLines: List[(Int, Int)], foundLines: List[(Int, Int)]) = {
300+
expectedLines.foreach({ case (line, expNr) =>
301+
foundLines.find(_._1 == line) match {
302+
case Some((_, `expNr`)) => // this line is ok
303+
case Some((_, foundNr)) => errorMsg(fileName, Some(line), expNr, foundNr)
304+
case None => errorMsg(fileName, Some(line), expNr, 0)
305+
}
306+
})
307+
foundLines.foreach({ case (line, foundNr) =>
308+
expectedLines.find(_._1 == line) match {
309+
case Some((_, `foundNr`)) => // this line is ok
310+
case Some((_, expNr)) => errorMsg(fileName, Some(line), expNr, foundNr)
311+
case None => errorMsg(fileName, Some(line), 0, foundNr)
312+
}
313+
})
314+
}
315+
316+
// ========== PARTEST HELPERS =============
317+
188318
// In particular, don't copy flags from scalac tests
189319
private val extensionsToCopy = scala.collection.immutable.HashSet("scala", "java")
190320

tests/neg/abstract-override.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
trait T { def foo: Int }
2-
trait T1 extends T { override def foo = super.foo }
3-
trait T2 extends T { override def foo = super.foo }
2+
trait T1 extends T { override def foo = super.foo } // error: method foo in trait T is accessed from super.
3+
trait T2 extends T { override def foo = super.foo } // error: method foo in trait T is accessed from super.
44
object Test extends T2 with T1 {
55
def main(args: Array[String]) = {
66
assert(foo == 3)

tests/neg/amp.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ object Test extends dotty.runtime.LegacyApp {
22

33
def foo() = {
44
def f: Int = 1
5-
val x = f _
5+
val x = f _ // error: not a function: => Int(f)
66
x
77
}
88

99
def bar(g: => Int) = {
10-
g _
10+
g _ // error: not a function: => Int(g)
1111
}
1212

13-
Console.println((bar{ Console.println("g called"); 42 })())
14-
Console.println(foo()())
13+
Console.println((bar{ Console.println("g called"); 42 })()) // error: method bar in object Test$ does not take more parameters
14+
Console.println(foo()()) // error: method foo in object Test$ does not take more parameters
1515
}

tests/neg/arrayclone-new.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object Test extends dotty.runtime.LegacyApp{
77
}
88

99
object ObjectArrayClone{
10-
val it : Array[String] = Array("1", "0");
10+
val it : Array[String] = Array("1", "0"); // error
1111
val cloned = it.clone();
1212
assert(cloned.sameElements(it));
1313
cloned(0) = "0";
@@ -22,7 +22,7 @@ object PolymorphicArrayClone{
2222
assert(it(0) == one)
2323
}
2424

25-
testIt(Array("one", "two"), "one", "two");
25+
testIt(Array("one", "two"), "one", "two"); // error
2626

2727
class Mangler[T: ClassTag](ts : T*){
2828
// this will always be a BoxedAnyArray even after we've unboxed its contents.

tests/neg/assignments.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ object assignments {
1313
x = x + 1
1414
x *= 2
1515

16-
x_= = 2 // should give missing arguments + reassignment to val
16+
x_= = 2 // error should give missing arguments + // error reassignment to val
1717
}
1818

1919
var c = new C
20-
import c._ // should give: prefix is not stable
20+
import c._ // error should give: prefix is not stable
2121
x = x + 1
2222
x *= 2
2323
}

tests/neg/autoTuplingTest.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import dotty.language.noAutoTupling
22

3-
object autoTuplingNeg {
3+
object autoTuplingNeg2 {
44

5-
val x = Some(1, 2)
5+
val x = Some(1, 2) // error: too many arguments for method apply: (x: A)Some[A]
66

77
x match {
8-
case Some(a, b) => a + b
8+
case Some(a, b) => a + b // error: wrong number of argument patterns for Some // error: not found: b
99
case None =>
1010
}
1111
}

tests/neg/blockescapesNeg.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object blockescapesNeg {
22
def m0 = { object Foo { class Bar { val field = 2 }} ; new Foo.Bar }
3-
m0.field
3+
m0.field // error
44
class A[T]
55
def m1 = { val x = 1; new A[x.type]}
66
}

tests/neg/bounds.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
object Test {
22
def g[B >: String <: Int](x: B): Int = x
33
def main(args: Array[String]): Unit = {
4-
g("foo")
4+
g("foo") // error: Type argument String' does not conform to upper bound Int
55
}
66
def baz[X >: Y, Y <: String](x: X, y: Y) = (x, y)
77

8-
baz[Int, String](1, "abc")
8+
baz[Int, String](1, "abc") // error: Type argument Int does not conform to lower bound Y
99

1010
}

tests/neg/boundspropagation.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ object test2 {
1313

1414

1515
def f(x: Any): Tree[Null] = x match {
16-
case y: Tree[_] => y
16+
case y: Tree[_] => y // error
1717
}
1818
}
1919
object test3 {
2020
class Tree[+T >: Null]
2121

2222

2323
def f(x: Any): Tree[Null] = x match {
24-
case y: Tree[_] => y
24+
case y: Tree[_] => y // error
2525
}
2626
}
2727

@@ -34,11 +34,11 @@ object test4 {
3434
class Tree[-S, -T >: Option[S]]
3535

3636
def g(x: Any): Tree[_, _ <: Option[N]] = x match {
37-
case y: Tree[_, _] => y
37+
case y: Tree[_, _] => y // error
3838
}
3939
}
4040
}
4141

4242
class Test5 {
43-
"": ({ type U = this.type })#U
43+
"": ({ type U = this.type })#U // error // error
4444
}

tests/neg/companions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ object companionsNeg {
88

99
{ object C {
1010
private val p = 1
11-
println(new C().q)
11+
println(new C().q) // error
1212
}}
1313
}
1414

tests/neg/cycles.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ class C {
2222

2323
class E {
2424
class F {
25-
type T <: x.type // error: not stable
26-
val z: x.type = ??? // error: not stable
25+
type T <: x.type // old-error: not stable
26+
val z: x.type = ??? // old-error: not stable
2727
}
2828
lazy val x: F#T = ???
2929
}
@@ -37,6 +37,6 @@ class T2 {
3737
type U = X | Int
3838
}
3939
object T12 {
40-
??? : (T1 {})#U // error: conflicting bounds
41-
??? : (T2 {})#U // error: conflicting bounds
40+
??? : (T1 {})#U // old-error: conflicting bounds
41+
??? : (T2 {})#U // old-error: conflicting bounds
4242
}

tests/neg/escapingRefs.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ object O {
33
class B
44
def f[T](x: T, y: T): T = y
55

6-
val x: A = f(new A { }, new B { })
6+
val x: A = f(new A { }, new B { }) // error
77

88
val y = f({ class C { def member: Int = 1 }; new C }, { class C { def member: Int = 1 }; new C })
9-
val z = y.member
9+
val z = y.member // error
1010
}

tests/neg/final-sealed.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
final class A
2-
class B extends A
3-
class C extends Option[Int]
2+
class B extends A // error: cannot extend final class A
3+
class C extends Option[Int] // error: cannot extend sealed class Option in different compilation unit
44

tests/neg/firstError.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.
1+
. // error: expected class or object definition
22

3-
\u890u3084eu
3+
\u890u3084eu // error: error in unicode escape // error: illegal character '\uffff'
44

tests/neg/i0091-infpaths.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ object infpaths {
22

33
object a {
44
trait T { t =>
5-
type M <: t.b.M
5+
type M <: t.b.M // error
66
type T <: a.T
77
val b: t.T
88
}
99
val x: a.T = ???
1010
}
1111

1212
val m1: a.x.M = ???
13-
val m2: a.x.b.M = m1
14-
val m3: a.x.b.b.M = m2
13+
val m2: a.x.b.M = m1 // error
14+
val m3: a.x.b.b.M = m2 // error
1515

1616
}

tests/neg/i0248-inherit-refined.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
object test {
22
class A { type T }
33
type X = A { type T = Int }
4-
class B extends X
4+
class B extends X // error
55
type Y = A & B
6-
class C extends Y
6+
class C extends Y // error
77
type Z = A | B
8-
class D extends Z
9-
abstract class E extends ({ val x: Int })
8+
class D extends Z // error
9+
abstract class E extends ({ val x: Int }) // error
1010
}

0 commit comments

Comments
 (0)