Skip to content

Commit 337efb2

Browse files
Fix: Parallel testing should have a fast path for the 1-class-in-module case (#5040)
#4869 --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent d1fa5e7 commit 337efb2

File tree

10 files changed

+96
-40
lines changed

10 files changed

+96
-40
lines changed

example/androidlib/java/1-hello-world/build.mill

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ object app extends AndroidAppModule {
107107
> ./mill show app.test
108108
...compiling 2 Java source...
109109

110-
> cat out/app/test/testForked.dest/worker-0/out.json
110+
> cat out/app/test/testForked.dest/out.json
111111
["",[{"fullyQualifiedName":"com.helloworld.ExampleUnitTest.textSize_isCorrect","selector":"com.helloworld.ExampleUnitTest.textSize_isCorrect","duration":...,"status":"Success"}]]
112112

113113
*/

example/depth/sandbox/2-test/build.mill

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ object bar extends MyModule
4747
/** Usage
4848

4949
> find . | grep generated.html
50-
.../out/foo/test/testForked.dest/worker-0/sandbox/generated.html
51-
.../out/bar/test/testForked.dest/worker-0/sandbox/generated.html
50+
.../out/foo/test/testForked.dest/sandbox/generated.html
51+
.../out/bar/test/testForked.dest/sandbox/generated.html
5252

53-
> cat out/foo/test/testForked.dest/worker-0/sandbox/generated.html
53+
> cat out/foo/test/testForked.dest/sandbox/generated.html
5454
<h1>hello</h1>
5555

56-
> cat out/bar/test/testForked.dest/worker-0/sandbox/generated.html
56+
> cat out/bar/test/testForked.dest/sandbox/generated.html
5757
<p>world</p>
5858

5959
*/

example/depth/sandbox/3-breaking/build.mill

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ object foo extends JavaModule {
3636

3737
> find . | grep .html
3838
...
39-
.../out/foo/test/testForked.dest/worker-0/sandbox/foo.html
39+
.../out/foo/test/testForked.dest/sandbox/foo.html
4040

41-
> cat out/foo/test/testForked.dest/worker-0/sandbox/foo.html
41+
> cat out/foo/test/testForked.dest/sandbox/foo.html
4242
<h1>foo</h1>
4343

4444
*/

libs/scalalib/src/mill/scalalib/TestModuleUtil.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ private final class TestModuleUtil(
5858
"\nRun discoveredTestClasses to see available tests"
5959
)
6060

61+
/** This is filtered by mill. */
6162
val filteredClassLists0 = testClassLists.map(_.filter(globFilter)).filter(_.nonEmpty)
6263

64+
/** This is filtered by the test framework. */
6365
val filteredClassLists =
6466
if (filteredClassLists0.size == 1 && !testParallelism) filteredClassLists0
6567
else {
@@ -102,7 +104,10 @@ private final class TestModuleUtil(
102104
}
103105
if (selectors.nonEmpty && filteredClassLists.isEmpty) throw doesNotMatchError
104106

105-
val result = if (testParallelism) {
107+
/** If we only have a single group with one test there is no point in running things in parallel. */
108+
val parallelismIsUseless =
109+
filteredClassLists.sizeIs == 1 && filteredClassLists.forall(_.sizeIs == 1)
110+
val result = if (testParallelism && !parallelismIsUseless) {
106111
runTestQueueScheduler(filteredClassLists)
107112
} else {
108113
runTestDefault(filteredClassLists)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package utestSingleTest.src
2+
3+
import utest._
4+
5+
object FooTests extends TestSuite {
6+
val tests = Tests {
7+
test("test") {
8+
true
9+
}
10+
}
11+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package mill.scalalib
2+
3+
import mill.T
4+
import mill.define.Discover
5+
import mill.testkit.{TestBaseModule, UnitTester}
6+
import utest.*
7+
import mill.util.TokenReaders.*
8+
9+
object TestRunnerParallelismTests extends TestSuite {
10+
object utestSingleTest extends TestBaseModule with ScalaModule with TestModule.Utest {
11+
override def scalaVersion: T[String] = sys.props.getOrElse("TEST_SCALA_2_13_VERSION", ???)
12+
13+
override def utestVersion = sys.props.getOrElse("TEST_UTEST_VERSION", ???)
14+
15+
override def testParallelism = true
16+
17+
lazy val millDiscover = Discover[this.type]
18+
}
19+
20+
override def tests: Tests = Tests {
21+
test("single group with single test") - UnitTester(
22+
utestSingleTest,
23+
os.Path(sys.env("MILL_TEST_RESOURCE_DIR")) / "utestSingleTest"
24+
).scoped { eval =>
25+
val Right(result) = eval.apply(utestSingleTest.testForked()): @unchecked
26+
val (doneMsg @ _, results) = result.value
27+
// Only one test should have been run
28+
assert(results.size == 1)
29+
// If we are actually running in serial mode (because there was only 1 group and 1 test case, so we disabled
30+
// parallelism) there should be no 'worker-X' directories and the 'sandbox' should be at the top level.
31+
assert(os.exists(eval.outPath / "testForked.dest" / "sandbox"))
32+
}
33+
}
34+
35+
}

libs/scalalib/test/src/mill/scalalib/TestRunnerScalatestTests.scala

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,23 @@ object TestRunnerScalatestTests extends TestSuite {
2929

3030
test("singleClass") - tester.testOnly(
3131
Seq("mill.scalalib.ScalaTestSpec"),
32-
3,
33-
Map(
34-
// No test grouping is triggered because we only run one test class
35-
testrunner.scalatest -> Set(
36-
"out.json",
37-
"result.log",
38-
"sandbox",
39-
"test-report.xml",
40-
"testargs"
41-
),
42-
testrunnerGrouping.scalatest -> Set(
32+
3, {
33+
val results = Set(
4334
"out.json",
4435
"result.log",
4536
"sandbox",
4637
"test-report.xml",
4738
"testargs"
48-
),
49-
testrunnerWorkStealing.scalatest -> Set("worker-0", "test-classes", "test-report.xml")
50-
)
39+
)
40+
41+
Map(
42+
testrunner.scalatest -> results,
43+
// No test grouping is triggered because we only run one test class
44+
testrunnerGrouping.scalatest -> results,
45+
// No workers are triggered because we only run one test class
46+
testrunnerWorkStealing.scalatest -> results
47+
)
48+
}
5149
)
5250

5351
// Runs three test classes with 3 test cases each, and trigger test grouping

libs/scalalib/test/src/mill/scalalib/TestRunnerTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ object TestRunnerTests extends TestSuite {
2626
junitReportIn(eval.outPath, "doneMessageFailure").shouldHave(1 -> Status.Failure)
2727
}
2828
}
29+
2930
test("success") {
3031
val outStream = new ByteArrayOutputStream()
3132
UnitTester(

libs/scalalib/test/src/mill/scalalib/TestRunnerUtestTests.scala

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,22 @@ object TestRunnerUtestTests extends TestSuite {
3232
test("prefix") - tester.testOnly(Seq("mill.scalalib.FooT*"), 1)
3333
test("exactly") - tester.testOnly(
3434
Seq("mill.scalalib.FooTests"),
35-
1,
36-
Map(
37-
testrunner.utest -> Set(
38-
"out.json",
39-
"result.log",
40-
"sandbox",
41-
"test-report.xml",
42-
"testargs"
43-
),
44-
// When there is only one test group with test classes, we do not put it in a subfolder
45-
testrunnerGrouping.utest -> Set(
35+
1, {
36+
val results = Set(
4637
"out.json",
4738
"result.log",
4839
"sandbox",
4940
"test-report.xml",
5041
"testargs"
51-
),
52-
testrunnerWorkStealing.utest -> Set("worker-0", "test-classes", "test-report.xml")
53-
)
42+
)
43+
Map(
44+
testrunner.utest -> results,
45+
// When there is only one test group with test classes, we do not put it in a subfolder
46+
testrunnerGrouping.utest -> results,
47+
// When there is only one test group with test classes, we do not run workers
48+
testrunnerWorkStealing.utest -> results
49+
)
50+
}
5451
)
5552
test("multi") - tester.testOnly(
5653
Seq("*Bar*", "*bar*"),

testkit/src/mill/testkit/ExampleTester.scala

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,10 @@ class ExampleTester(
112112
case s"curl $rest" => s"curl --retry 7 --retry-all-errors $rest"
113113
case s => s
114114
}
115-
Console.err.println(s"$workspacePath> $commandStr")
115+
116+
/** The command we're about to execute */
117+
val debugCommandStr = s"$workspacePath> $commandStr"
118+
Console.err.println(debugCommandStr)
116119
Console.err.println(
117120
s"""--- Expected output ----------
118121
|${expectedSnippets.mkString("\n")}
@@ -142,14 +145,16 @@ class ExampleTester(
142145
fansi.Str(res.out.text(), errorMode = fansi.ErrorMode.Strip).plainText,
143146
fansi.Str(res.err.text(), errorMode = fansi.ErrorMode.Strip).plainText
144147
),
145-
check
148+
check,
149+
debugCommandStr
146150
)
147151
}
148152

149153
def validateEval(
150154
expectedSnippets: Vector[String],
151155
evalResult: IntegrationTester.EvalResult,
152-
check: Boolean = true
156+
check: Boolean = true,
157+
command: String = ""
153158
): Unit = {
154159
if (check) {
155160
if (expectedSnippets.exists(_.startsWith("error: "))) assert(!evalResult.isSuccess)
@@ -181,7 +186,11 @@ class ExampleTester(
181186
for (expectedLine <- unwrappedExpected.linesIterator) {
182187
Predef.assert(
183188
filteredOut.linesIterator.exists(globMatches(expectedLine, _)),
184-
s"==== filteredOut:\n$filteredOut\n==== Missing expectedLine: \n$expectedLine"
189+
(if (command == "") "" else s"==== command:\n$command\n") +
190+
s"""==== filteredOut:
191+
|$filteredOut
192+
|==== Missing expectedLine:
193+
|$expectedLine""".stripMargin
185194
)
186195
}
187196
}

0 commit comments

Comments
 (0)