@@ -3,9 +3,12 @@ package test
3
3
import dotty .partest .DPConfig
4
4
import dotty .tools .dotc .{Main , Bench , Driver }
5
5
import dotty .tools .dotc .reporting .Reporter
6
+ import dotty .tools .dotc .util .SourcePosition
7
+ import dotty .tools .dotc .config .CompilerCommand
6
8
import scala .collection .mutable .ListBuffer
7
- import scala .reflect .io .{ Path , Directory , File => SFile }
9
+ import scala .reflect .io .{ Path , Directory , File => SFile , AbstractFile }
8
10
import scala .tools .partest .nest .{ FileManager , NestUI }
11
+ import scala .annotation .tailrec
9
12
import java .io .{ RandomAccessFile , File => JFile }
10
13
11
14
import org .junit .Test
@@ -178,13 +181,140 @@ abstract class CompilerTest {
178
181
179
182
// ========== HELPERS =============
180
183
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 = {
182
186
val allArgs = args ++ defaultOptions
183
187
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
185
191
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
+ }
186
229
}
187
230
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
+
188
318
// In particular, don't copy flags from scalac tests
189
319
private val extensionsToCopy = scala.collection.immutable.HashSet (" scala" , " java" )
190
320
0 commit comments