Skip to content

Commit bf808b3

Browse files
authored
Add an option to only rerun tests that have failed (#16263)
- testCompilation supports running only failed tests with --failed argument fixes #3798
2 parents d84007c + 6dfc668 commit bf808b3

16 files changed

+82
-23
lines changed

compiler/test/dotc/comptest.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ object comptest extends ParallelTesting {
1212
def isInteractive = true
1313
def testFilter = Nil
1414
def updateCheckFiles: Boolean = false
15+
def failedTests = None
1516

1617
val posDir = "./tests/pos/"
1718
val negDir = "./tests/neg/"

compiler/test/dotty/Properties.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ object Properties {
1313
prop == null || prop == "TRUE"
1414
}
1515

16+
/** If property is unset or FALSE we consider it `false` */
17+
private def propIsTrue(name: String): Boolean =
18+
sys.props.getOrElse(name, "FALSE") == "TRUE"
19+
1620
/** Are we running on the CI? */
1721
val isRunByCI: Boolean = sys.env.isDefinedAt("DOTTY_CI_RUN")
1822
|| sys.env.isDefinedAt("DRONE") // TODO remove this when we drop Drone
@@ -30,9 +34,11 @@ object Properties {
3034
*/
3135
val testsFilter: List[String] = sys.props.get("dotty.tests.filter").fold(Nil)(_.split(',').toList)
3236

37+
/** Run only failed tests */
38+
val rerunFailed: Boolean = propIsTrue("dotty.tests.rerunFailed")
39+
3340
/** Tests should override the checkfiles with the current output */
34-
val testsUpdateCheckfile: Boolean =
35-
sys.props.getOrElse("dotty.tests.updateCheckfiles", "FALSE") == "TRUE"
41+
val testsUpdateCheckfile: Boolean = propIsTrue("dotty.tests.updateCheckfiles")
3642

3743
/** When set, the run tests are only compiled - not run, a warning will be
3844
* issued

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.junit.Assume._
1010
import org.junit.experimental.categories.Category
1111

1212
import scala.concurrent.duration._
13+
import reporting.TestReporter
1314
import vulpix._
1415

1516
import java.nio.file._
@@ -214,6 +215,7 @@ object BootstrappedOnlyCompilationTests extends ParallelTesting {
214215
def isInteractive = SummaryReport.isInteractive
215216
def testFilter = Properties.testsFilter
216217
def updateCheckFiles: Boolean = Properties.testsUpdateCheckfile
218+
def failedTests = TestReporter.lastRunFailedTests
217219

218220
implicit val summaryReport: SummaryReporting = new SummaryReport
219221
@AfterClass def tearDown(): Unit = {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import scala.jdk.CollectionConverters._
1616
import scala.util.matching.Regex
1717
import scala.concurrent.duration._
1818
import TestSources.sources
19+
import reporting.TestReporter
1920
import vulpix._
2021

2122
class CompilationTests {
@@ -317,6 +318,7 @@ object CompilationTests extends ParallelTesting {
317318
def isInteractive = SummaryReport.isInteractive
318319
def testFilter = Properties.testsFilter
319320
def updateCheckFiles: Boolean = Properties.testsUpdateCheckfile
321+
def failedTests = TestReporter.lastRunFailedTests
320322

321323
implicit val summaryReport: SummaryReporting = new SummaryReport
322324
@AfterClass def tearDown(): Unit = {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package dotc
55
import scala.language.unsafeNulls
66

77
import org.junit.{AfterClass, Test}
8+
import reporting.TestReporter
89
import vulpix._
910

1011
import java.io.{File => JFile}
@@ -48,6 +49,7 @@ object FromTastyTests extends ParallelTesting {
4849
def isInteractive = SummaryReport.isInteractive
4950
def testFilter = Properties.testsFilter
5051
def updateCheckFiles: Boolean = Properties.testsUpdateCheckfile
52+
def failedTests = TestReporter.lastRunFailedTests
5153

5254
implicit val summaryReport: SummaryReporting = new SummaryReport
5355
@AfterClass def tearDown(): Unit = {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.junit.{AfterClass, Test}
1212
import org.junit.experimental.categories.Category
1313

1414
import scala.concurrent.duration._
15+
import reporting.TestReporter
1516
import vulpix._
1617

1718

@@ -76,6 +77,7 @@ object IdempotencyTests extends ParallelTesting {
7677
def isInteractive = SummaryReport.isInteractive
7778
def testFilter = Properties.testsFilter
7879
def updateCheckFiles: Boolean = Properties.testsUpdateCheckfile
80+
def failedTests = TestReporter.lastRunFailedTests
7981

8082
implicit val summaryReport: SummaryReporting = new SummaryReport
8183
@AfterClass def tearDown(): Unit = {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import scala.util.matching.Regex
1717
import scala.concurrent.duration._
1818
import TestSources.sources
1919
import vulpix._
20+
import reporting.TestReporter
2021

2122
class TastyBootstrapTests {
2223
import ParallelTesting._
@@ -114,6 +115,7 @@ object TastyBootstrapTests extends ParallelTesting {
114115
def isInteractive = SummaryReport.isInteractive
115116
def testFilter = Properties.testsFilter
116117
def updateCheckFiles: Boolean = Properties.testsUpdateCheckfile
118+
def failedTests = TestReporter.lastRunFailedTests
117119

118120
implicit val summaryReport: SummaryReporting = new SummaryReport
119121
@AfterClass def tearDown(): Unit = {

compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import org.junit.Test
44
import org.junit.AfterClass
55
import org.junit.Assert.*
66
import org.junit.experimental.categories.Category
7-
87
import dotty.{BootstrappedOnlyTests, Properties}
98
import dotty.tools.vulpix.*
109
import dotty.tools.vulpix.TestConfiguration.*
1110
import dotty.tools.dotc.Main
11+
import dotty.tools.dotc.reporting.TestReporter
1212

13-
import java.nio.file.{Files, FileSystems, Path, Paths, StandardCopyOption}
13+
import java.nio.file.{FileSystems, Files, Path, Paths, StandardCopyOption}
1414
import scala.jdk.CollectionConverters.*
1515
import scala.util.Properties.userDir
1616
import scala.language.unsafeNulls
@@ -85,6 +85,7 @@ object CoverageTests extends ParallelTesting:
8585
def testFilter = Properties.testsFilter
8686
def isInteractive = SummaryReport.isInteractive
8787
def updateCheckFiles = Properties.testsUpdateCheckfile
88+
def failedTests = TestReporter.lastRunFailedTests
8889

8990
given summaryReport: SummaryReporting = SummaryReport()
9091
@AfterClass def tearDown(): Unit =

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

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@ package dotc
33
package reporting
44

55
import scala.language.unsafeNulls
6-
7-
import java.io.{ PrintStream, PrintWriter, File => JFile, FileOutputStream, StringWriter }
6+
import java.io.{BufferedReader, FileInputStream, FileOutputStream, FileReader, PrintStream, PrintWriter, StringReader, StringWriter, File as JFile}
87
import java.text.SimpleDateFormat
98
import java.util.Date
10-
import core.Decorators._
9+
import core.Decorators.*
1110

1211
import scala.collection.mutable
13-
12+
import scala.jdk.CollectionConverters.*
1413
import util.SourcePosition
15-
import core.Contexts._
16-
import Diagnostic._
17-
import interfaces.Diagnostic.{ ERROR, WARNING }
14+
import core.Contexts.*
15+
import Diagnostic.*
16+
import dotty.Properties
17+
import interfaces.Diagnostic.{ERROR, WARNING}
18+
19+
import scala.io.Codec
1820

1921
class TestReporter protected (outWriter: PrintWriter, filePrintln: String => Unit, logLevel: Int)
2022
extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering {
@@ -84,17 +86,23 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M
8486
}
8587

8688
object TestReporter {
89+
private val testLogsDirName: String = "testlogs"
90+
private val failedTestsFileName: String = "last-failed.log"
91+
private val failedTestsFile: JFile = new JFile(s"$testLogsDirName/$failedTestsFileName")
92+
8793
private var outFile: JFile = _
8894
private var logWriter: PrintWriter = _
95+
private var failedTestsWriter: PrintWriter = _
8996

9097
private def initLog() = if (logWriter eq null) {
9198
val date = new Date
9299
val df0 = new SimpleDateFormat("yyyy-MM-dd")
93100
val df1 = new SimpleDateFormat("yyyy-MM-dd-'T'HH-mm-ss")
94-
val folder = s"testlogs/tests-${df0.format(date)}"
101+
val folder = s"$testLogsDirName/tests-${df0.format(date)}"
95102
new JFile(folder).mkdirs()
96103
outFile = new JFile(s"$folder/tests-${df1.format(date)}.log")
97104
logWriter = new PrintWriter(new FileOutputStream(outFile, true))
105+
failedTestsWriter = new PrintWriter(new FileOutputStream(failedTestsFile, false))
98106
}
99107

100108
def logPrintln(str: String) = {
@@ -144,4 +152,16 @@ object TestReporter {
144152
}
145153
rep
146154
}
155+
156+
def lastRunFailedTests: Option[List[String]] =
157+
Option.when(
158+
Properties.rerunFailed &&
159+
failedTestsFile.exists() &&
160+
failedTestsFile.isFile
161+
)(java.nio.file.Files.readAllLines(failedTestsFile.toPath).asScala.toList)
162+
163+
def writeFailedTests(tests: List[String]): Unit =
164+
initLog()
165+
tests.foreach(failed => failedTestsWriter.println(failed))
166+
failedTestsWriter.flush()
147167
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package dotty.tools.vulpix
2+
3+
case class FailedTestInfo(title: String, extra: String)

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ trait ParallelTesting extends RunnerOrchestration { self =>
5757
/** Tests should override the checkfiles with the current output */
5858
def updateCheckFiles: Boolean
5959

60+
/** Contains a list of failed tests to run, if list is empty no tests will run */
61+
def failedTests: Option[List[String]]
62+
6063
/** A test source whose files or directory of files is to be compiled
6164
* in a specific way defined by the `Test`
6265
*/
@@ -204,6 +207,14 @@ trait ParallelTesting extends RunnerOrchestration { self =>
204207

205208
protected def shouldSkipTestSource(testSource: TestSource): Boolean = false
206209

210+
protected def shouldReRun(testSource: TestSource): Boolean =
211+
failedTests.forall(rerun => testSource match {
212+
case JointCompilationSource(_, files, _, _, _, _) =>
213+
rerun.exists(filter => files.exists(file => file.getPath.contains(filter)))
214+
case SeparateCompilationSource(_, dir, _, _) =>
215+
rerun.exists(dir.getPath.contains)
216+
})
217+
207218
private trait CompilationLogic { this: Test =>
208219
def suppressErrors = false
209220

@@ -359,7 +370,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
359370
case SeparateCompilationSource(_, dir, _, _) =>
360371
testFilter.exists(dir.getPath.contains)
361372
}
362-
filteredByName.filterNot(shouldSkipTestSource(_))
373+
filteredByName.filterNot(shouldSkipTestSource(_)).filter(shouldReRun(_))
363374

364375
/** Total amount of test sources being compiled by this test */
365376
val sourceCount = filteredSources.length
@@ -409,14 +420,14 @@ trait ParallelTesting extends RunnerOrchestration { self =>
409420
synchronized { reproduceInstructions.append(ins) }
410421

411422
/** The test sources that failed according to the implementing subclass */
412-
private val failedTestSources = mutable.ArrayBuffer.empty[String]
423+
private val failedTestSources = mutable.ArrayBuffer.empty[FailedTestInfo]
413424
protected final def failTestSource(testSource: TestSource, reason: Failure = Generic) = synchronized {
414425
val extra = reason match {
415426
case TimeoutFailure(title) => s", test '$title' timed out"
416427
case JavaCompilationFailure(msg) => s", java test sources failed to compile with: \n$msg"
417428
case Generic => ""
418429
}
419-
failedTestSources.append(testSource.title + s" failed" + extra)
430+
failedTestSources.append(FailedTestInfo(testSource.title, s" failed" + extra))
420431
fail(reason)
421432
}
422433

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package tools
33
package vulpix
44

55
import scala.language.unsafeNulls
6-
76
import scala.collection.mutable
87
import dotc.reporting.TestReporter
98

@@ -23,7 +22,7 @@ trait SummaryReporting {
2322
def reportPassed(): Unit
2423

2524
/** Add the name of the failed test */
26-
def addFailedTest(msg: String): Unit
25+
def addFailedTest(msg: FailedTestInfo): Unit
2726

2827
/** Add instructions to reproduce the error */
2928
def addReproduceInstruction(instr: String): Unit
@@ -49,7 +48,7 @@ trait SummaryReporting {
4948
final class NoSummaryReport extends SummaryReporting {
5049
def reportFailed(): Unit = ()
5150
def reportPassed(): Unit = ()
52-
def addFailedTest(msg: String): Unit = ()
51+
def addFailedTest(msg: FailedTestInfo): Unit = ()
5352
def addReproduceInstruction(instr: String): Unit = ()
5453
def addStartingMessage(msg: String): Unit = ()
5554
def addCleanup(f: () => Unit): Unit = ()
@@ -66,7 +65,7 @@ final class SummaryReport extends SummaryReporting {
6665
import scala.jdk.CollectionConverters._
6766

6867
private val startingMessages = new java.util.concurrent.ConcurrentLinkedDeque[String]
69-
private val failedTests = new java.util.concurrent.ConcurrentLinkedDeque[String]
68+
private val failedTests = new java.util.concurrent.ConcurrentLinkedDeque[FailedTestInfo]
7069
private val reproduceInstructions = new java.util.concurrent.ConcurrentLinkedDeque[String]
7170
private val cleanUps = new java.util.concurrent.ConcurrentLinkedDeque[() => Unit]
7271

@@ -79,7 +78,7 @@ final class SummaryReport extends SummaryReporting {
7978
def reportPassed(): Unit =
8079
passed += 1
8180

82-
def addFailedTest(msg: String): Unit =
81+
def addFailedTest(msg: FailedTestInfo): Unit =
8382
failedTests.add(msg)
8483

8584
def addReproduceInstruction(instr: String): Unit =
@@ -108,7 +107,8 @@ final class SummaryReport extends SummaryReporting {
108107

109108
startingMessages.asScala.foreach(rep.append)
110109

111-
failedTests.asScala.map(x => s" $x\n").foreach(rep.append)
110+
failedTests.asScala.map(x => s" ${x.title}${x.extra}\n").foreach(rep.append)
111+
TestReporter.writeFailedTests(failedTests.asScala.toList.map(_.title))
112112

113113
// If we're compiling locally, we don't need instructions on how to
114114
// reproduce failures

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ object VulpixMetaTests extends ParallelTesting {
3030
def isInteractive = false // Don't beautify output for interactive use.
3131
def testFilter = Nil // Run all the tests.
3232
def updateCheckFiles: Boolean = false
33+
def failedTests = None
3334

3435
@AfterClass
3536
def tearDown() = this.cleanup()

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ object VulpixUnitTests extends ParallelTesting {
108108
def isInteractive = !sys.env.contains("DRONE")
109109
def testFilter = Nil
110110
def updateCheckFiles: Boolean = false
111+
def failedTests = None
111112

112113
@AfterClass
113114
def tearDown() = this.cleanup()

project/Build.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -607,14 +607,15 @@ object Build {
607607
if (args.contains("--help")) {
608608
println(
609609
s"""
610-
|usage: testCompilation [--help] [--from-tasty] [--update-checkfiles] [<filter>]
610+
|usage: testCompilation [--help] [--from-tasty] [--update-checkfiles] [--failed] [<filter>]
611611
|
612612
|By default runs tests in dotty.tools.dotc.*CompilationTests and dotty.tools.dotc.coverage.*,
613613
|excluding tests tagged with dotty.SlowTests.
614614
|
615615
| --help show this message
616616
| --from-tasty runs tests in dotty.tools.dotc.FromTastyTests
617617
| --update-checkfiles override the checkfiles that did not match with the current output
618+
| --failed re-run only failed tests
618619
| <filter> substring of the path of the tests file
619620
|
620621
""".stripMargin
@@ -623,11 +624,13 @@ object Build {
623624
}
624625
else {
625626
val updateCheckfile = args.contains("--update-checkfiles")
627+
val rerunFailed = args.contains("--failed")
626628
val fromTasty = args.contains("--from-tasty")
627-
val args1 = if (updateCheckfile | fromTasty) args.filter(x => x != "--update-checkfiles" && x != "--from-tasty") else args
629+
val args1 = if (updateCheckfile | fromTasty | rerunFailed) args.filter(x => x != "--update-checkfiles" && x != "--from-tasty" && x != "--failed") else args
628630
val test = if (fromTasty) "dotty.tools.dotc.FromTastyTests" else "dotty.tools.dotc.*CompilationTests dotty.tools.dotc.coverage.*"
629631
val cmd = s" $test -- --exclude-categories=dotty.SlowTests" +
630632
(if (updateCheckfile) " -Ddotty.tests.updateCheckfiles=TRUE" else "") +
633+
(if (rerunFailed) " -Ddotty.tests.rerunFailed=TRUE" else "") +
631634
(if (args1.nonEmpty) " -Ddotty.tests.filter=" + args1.mkString(" ") else "")
632635
(Test / testOnly).toTask(cmd)
633636
}

sjs-compiler-tests/test/scala/dotty/tools/dotc/ScalaJSCompilationTests.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.junit.{ Test, BeforeClass, AfterClass }
66
import org.junit.experimental.categories.Category
77

88
import scala.concurrent.duration._
9+
import reporting.TestReporter
910
import vulpix._
1011

1112
@Category(Array(classOf[ScalaJSCompilationTests]))
@@ -23,6 +24,7 @@ class ScalaJSCompilationTests extends ParallelTesting {
2324
def isInteractive = SummaryReport.isInteractive
2425
def testFilter = Properties.testsFilter
2526
def updateCheckFiles: Boolean = Properties.testsUpdateCheckfile
27+
def failedTests = TestReporter.lastRunFailedTests
2628

2729
// Negative tests ------------------------------------------------------------
2830

0 commit comments

Comments
 (0)