Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Neg tests check files for // error markers (rebased and updated) #1106

Merged
merged 6 commits into from
Feb 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/dotty/tools/dotc/reporting/Reporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ abstract class Reporter {
var warningCount = 0
def hasErrors = errorCount > 0
def hasWarnings = warningCount > 0
private var errors: List[Error] = Nil
def allErrors = errors

/** Have errors been reported by this reporter, or in the
* case where this is a StoreReporter, by an outer reporter?
Expand All @@ -238,7 +240,9 @@ abstract class Reporter {
d match {
case d: ConditionalWarning if !d.enablingOption.value => unreportedWarnings(d.enablingOption.name) += 1
case d: Warning => warningCount += 1
case d: Error => errorCount += 1
case d: Error =>
errors = d :: errors
errorCount += 1
case d: Info => // nothing to do here
// match error if d is something else
}
Expand Down
136 changes: 133 additions & 3 deletions test/test/CompilerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package test
import dotty.partest.DPConfig
import dotty.tools.dotc.{Main, Bench, Driver}
import dotty.tools.dotc.reporting.Reporter
import dotty.tools.dotc.util.SourcePosition
import dotty.tools.dotc.config.CompilerCommand
import scala.collection.mutable.ListBuffer
import scala.reflect.io.{ Path, Directory, File => SFile }
import scala.reflect.io.{ Path, Directory, File => SFile, AbstractFile }
import scala.tools.partest.nest.{ FileManager, NestUI }
import scala.annotation.tailrec
import java.io.{ RandomAccessFile, File => JFile }

import org.junit.Test
Expand Down Expand Up @@ -178,13 +181,140 @@ abstract class CompilerTest {

// ========== HELPERS =============

private def compileArgs(args: Array[String], xerrors: Int = 0)(implicit defaultOptions: List[String]): Unit = {
private def compileArgs(args: Array[String], xerrors: Int = 0)
(implicit defaultOptions: List[String]): Unit = {
val allArgs = args ++ defaultOptions
val processor = if (allArgs.exists(_.startsWith("#"))) Bench else Main
val nerrors = processor.process(allArgs).errorCount
val reporter = processor.process(allArgs)

val nerrors = reporter.errorCount
assert(nerrors == xerrors, s"Wrong # of errors. Expected: $xerrors, found: $nerrors")

// NEG TEST
if (xerrors > 0) {
val errorLines = reporter.allErrors.map(_.pos)
// reporter didn't record as many errors as its errorCount says
assert(errorLines.length == nerrors, s"Not enough errors recorded.")

val allFiles = (allArgs filter {
arg => !arg.startsWith("-") && (arg.endsWith(".scala") || arg.endsWith(".java"))
}).toList
val expectedErrorsPerFile = allFiles.map(getErrors(_))

// Some compiler errors have an associated source position. Each error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose we change all compiler errors to require a source position.
Than neg tests will not need to take xerrors as arguments and keep it updated both in file and in test configuration.
Additionally, this would allow to have a neg-dir instead of test-per-neg-test, where every file specifies how many errors it should have with // error comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DarkDimius If we decide to do it I propose to open separate pull request for it. This pull request already has long history.

// needs to correspond to a "// error" marker on that line in the source
// file and vice versa.
// Other compiler errors don't have an associated source position. Their
// number should correspond to the total count of "// nopos-error"
// markers in all files
val (errorsByFile, errorsWithoutPos) = errorLines.groupBy(_.source.file).toList.partition(_._1.toString != "<no source>")

// check errors with source position
val foundErrorsPerFile = errorsByFile.map({ case (fileName, errorList) =>
val posErrorLinesToNr = errorList.groupBy(_.line).toList.map({ case (line, list) => (line, list.length) }).sortBy(_._1)
ErrorsInFile(fileName.toString, 0, posErrorLinesToNr)
})
val expectedErrorsPerFileZeroed = expectedErrorsPerFile.map({
case ErrorsInFile(fileName, _, posErrorLinesToNr) =>
ErrorsInFile(fileName.toString, 0, posErrorLinesToNr)
})
checkErrorsWithPosition(expectedErrorsPerFileZeroed, foundErrorsPerFile)

// check errors without source position
val expectedNoPos = expectedErrorsPerFile.map(_.noposErrorNr).sum
val foundNoPos = errorsWithoutPos.map(_._2.length).sum
assert(foundNoPos == expectedNoPos,
s"Wrong # of errors without source position. Expected (all files): $expectedNoPos, found (compiler): $foundNoPos")
}
}

// ========== NEG TEST HELPERS =============

/** Captures the number of nopos-errors in the given file and the number of
* errors with a position, represented as a tuple of source line and number
* of errors on that line. */
case class ErrorsInFile(fileName: String, noposErrorNr: Int, posErrorLinesToNr: List[(Int, Int)])

/** Extracts the errors expected for the given neg test file. */
def getErrors(fileName: String): ErrorsInFile = {
val content = SFile(fileName).slurp
val (line, rest) = content.span(_ != '\n')

@tailrec
def checkLine(line: String, rest: String, index: Int, noposAcc: Int, posAcc: List[(Int, Int)]): ErrorsInFile = {
val posErrors = "// ?error".r.findAllIn(line).length
val newPosAcc = if (posErrors > 0) (index, posErrors) :: posAcc else posAcc
val newNoPosAcc = noposAcc + "// ?nopos-error".r.findAllIn(line).length
val (newLine, newRest) = rest.span(_ != '\n')
if (newRest.isEmpty)
ErrorsInFile(fileName.toString, newNoPosAcc, newPosAcc.reverse)
else
checkLine(newLine, newRest.tail, index + 1, newNoPosAcc, newPosAcc) // skip leading '\n'
}

checkLine(line, rest.tail, 0, 0, Nil) // skip leading '\n'
}

/** Asserts that the expected and found number of errors correspond, and
* otherwise throws an error with the filename, plus optionally a line
* number if available. */
def errorMsg(fileName: String, lineNumber: Option[Int], exp: Int, found: Int) = {
val i = lineNumber.map({ i => ":" + (i + 1) }).getOrElse("")
assert(found == exp, s"Wrong # of errors for $fileName$i. Expected (file): $exp, found (compiler): $found")
}

/** Compares the expected with the found errors and creates a nice error
* message if they don't agree. */
def checkErrorsWithPosition(expected: List[ErrorsInFile], found: List[ErrorsInFile]): Unit = {
// create nice error messages
expected.diff(found) match {
case Nil => // nothing missing
case ErrorsInFile(fileName, _, expectedLines) :: xs =>
found.find(_.fileName == fileName) match {
case None =>
// expected some errors, but none found for this file
errorMsg(fileName, None, expectedLines.map(_._2).sum, 0)
case Some(ErrorsInFile(_,_,foundLines)) =>
// found wrong number/location of markers for this file
compareLines(fileName, expectedLines, foundLines)
}
}

found.diff(expected) match {
case Nil => // nothing missing
case ErrorsInFile(fileName, _, foundLines) :: xs =>
expected.find(_.fileName == fileName) match {
case None =>
// found some errors, but none expected for this file
errorMsg(fileName, None, 0, foundLines.map(_._2).sum)
case Some(ErrorsInFile(_,_,expectedLines)) =>
// found wrong number/location of markers for this file
compareLines(fileName, expectedLines, foundLines)
}
}
}

/** Gives an error message for one line where the expected number of errors and
* the number of compiler errors differ. */
def compareLines(fileName: String, expectedLines: List[(Int, Int)], foundLines: List[(Int, Int)]) = {
expectedLines.foreach({ case (line, expNr) =>
foundLines.find(_._1 == line) match {
case Some((_, `expNr`)) => // this line is ok
case Some((_, foundNr)) => errorMsg(fileName, Some(line), expNr, foundNr)
case None => errorMsg(fileName, Some(line), expNr, 0)
}
})
foundLines.foreach({ case (line, foundNr) =>
expectedLines.find(_._1 == line) match {
case Some((_, `foundNr`)) => // this line is ok
case Some((_, expNr)) => errorMsg(fileName, Some(line), expNr, foundNr)
case None => errorMsg(fileName, Some(line), 0, foundNr)
}
})
}

// ========== PARTEST HELPERS =============

// In particular, don't copy flags from scalac tests
private val extensionsToCopy = scala.collection.immutable.HashSet("scala", "java")

Expand Down
4 changes: 2 additions & 2 deletions tests/neg/abstract-override.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
trait T { def foo: Int }
trait T1 extends T { override def foo = super.foo }
trait T2 extends T { override def foo = super.foo }
trait T1 extends T { override def foo = super.foo } // error: method foo in trait T is accessed from super.
trait T2 extends T { override def foo = super.foo } // error: method foo in trait T is accessed from super.
object Test extends T2 with T1 {
def main(args: Array[String]) = {
assert(foo == 3)
Expand Down
8 changes: 4 additions & 4 deletions tests/neg/amp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ object Test extends dotty.runtime.LegacyApp {

def foo() = {
def f: Int = 1
val x = f _
val x = f _ // error: not a function: => Int(f)
x
}

def bar(g: => Int) = {
g _
g _ // error: not a function: => Int(g)
}

Console.println((bar{ Console.println("g called"); 42 })())
Console.println(foo()())
Console.println((bar{ Console.println("g called"); 42 })()) // error: method bar in object Test$ does not take more parameters
Console.println(foo()()) // error: method foo in object Test$ does not take more parameters
}
4 changes: 2 additions & 2 deletions tests/neg/arrayclone-new.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ object Test extends dotty.runtime.LegacyApp{
}

object ObjectArrayClone{
val it : Array[String] = Array("1", "0");
val it : Array[String] = Array("1", "0"); // error
val cloned = it.clone();
assert(cloned.sameElements(it));
cloned(0) = "0";
Expand All @@ -22,7 +22,7 @@ object PolymorphicArrayClone{
assert(it(0) == one)
}

testIt(Array("one", "two"), "one", "two");
testIt(Array("one", "two"), "one", "two"); // error

class Mangler[T: ClassTag](ts : T*){
// this will always be a BoxedAnyArray even after we've unboxed its contents.
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/assignments.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ object assignments {
x = x + 1
x *= 2

x_= = 2 // should give missing arguments + reassignment to val
x_= = 2 // error should give missing arguments + // error reassignment to val
}

var c = new C
import c._ // should give: prefix is not stable
import c._ // error should give: prefix is not stable
x = x + 1
x *= 2
}
6 changes: 3 additions & 3 deletions tests/neg/autoTuplingTest.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import dotty.language.noAutoTupling

object autoTuplingNeg {
object autoTuplingNeg2 {

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

x match {
case Some(a, b) => a + b
case Some(a, b) => a + b // error: wrong number of argument patterns for Some // error: not found: b
case None =>
}
}
2 changes: 1 addition & 1 deletion tests/neg/blockescapesNeg.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
object blockescapesNeg {
def m0 = { object Foo { class Bar { val field = 2 }} ; new Foo.Bar }
m0.field
m0.field // error
class A[T]
def m1 = { val x = 1; new A[x.type]}
}
4 changes: 2 additions & 2 deletions tests/neg/bounds.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
object Test {
def g[B >: String <: Int](x: B): Int = x
def main(args: Array[String]): Unit = {
g("foo")
g("foo") // error: Type argument String' does not conform to upper bound Int
}
def baz[X >: Y, Y <: String](x: X, y: Y) = (x, y)

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

}
8 changes: 4 additions & 4 deletions tests/neg/boundspropagation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ object test2 {


def f(x: Any): Tree[Null] = x match {
case y: Tree[_] => y
case y: Tree[_] => y // error
}
}
object test3 {
class Tree[+T >: Null]


def f(x: Any): Tree[Null] = x match {
case y: Tree[_] => y
case y: Tree[_] => y // error
}
}

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

def g(x: Any): Tree[_, _ <: Option[N]] = x match {
case y: Tree[_, _] => y
case y: Tree[_, _] => y // error
}
}
}

class Test5 {
"": ({ type U = this.type })#U
"": ({ type U = this.type })#U // error // error
}
2 changes: 1 addition & 1 deletion tests/neg/companions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object companionsNeg {

{ object C {
private val p = 1
println(new C().q)
println(new C().q) // error
}}
}

Expand Down
8 changes: 4 additions & 4 deletions tests/neg/cycles.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ class C {

class E {
class F {
type T <: x.type // error: not stable
val z: x.type = ??? // error: not stable
type T <: x.type // old-error: not stable
val z: x.type = ??? // old-error: not stable
}
lazy val x: F#T = ???
}
Expand All @@ -37,6 +37,6 @@ class T2 {
type U = X | Int
}
object T12 {
??? : (T1 {})#U // error: conflicting bounds
??? : (T2 {})#U // error: conflicting bounds
??? : (T1 {})#U // old-error: conflicting bounds
??? : (T2 {})#U // old-error: conflicting bounds
}
4 changes: 2 additions & 2 deletions tests/neg/escapingRefs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ object O {
class B
def f[T](x: T, y: T): T = y

val x: A = f(new A { }, new B { })
val x: A = f(new A { }, new B { }) // error

val y = f({ class C { def member: Int = 1 }; new C }, { class C { def member: Int = 1 }; new C })
val z = y.member
val z = y.member // error
}
4 changes: 2 additions & 2 deletions tests/neg/final-sealed.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
final class A
class B extends A
class C extends Option[Int]
class B extends A // error: cannot extend final class A
class C extends Option[Int] // error: cannot extend sealed class Option in different compilation unit

4 changes: 2 additions & 2 deletions tests/neg/firstError.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.
. // error: expected class or object definition

\u890u3084eu
\u890u3084eu // error: error in unicode escape // error: illegal character '\uffff'

6 changes: 3 additions & 3 deletions tests/neg/i0091-infpaths.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ object infpaths {

object a {
trait T { t =>
type M <: t.b.M
type M <: t.b.M // error
type T <: a.T
val b: t.T
}
val x: a.T = ???
}

val m1: a.x.M = ???
val m2: a.x.b.M = m1
val m3: a.x.b.b.M = m2
val m2: a.x.b.M = m1 // error
val m3: a.x.b.b.M = m2 // error

}
8 changes: 4 additions & 4 deletions tests/neg/i0248-inherit-refined.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
object test {
class A { type T }
type X = A { type T = Int }
class B extends X
class B extends X // error
type Y = A & B
class C extends Y
class C extends Y // error
type Z = A | B
class D extends Z
abstract class E extends ({ val x: Int })
class D extends Z // error
abstract class E extends ({ val x: Int }) // error
}
Loading