Skip to content

Commit 4b66c1d

Browse files
lihaoyilefou
authored andcommitted
Fix uTest test task creation for multi-test-class test modules (com-lihaoyi#4811)
Seems to be caused by the `def getTestTasks` changes in com-lihaoyi#4737. The problem appears to be in uTest, which is strict about ensuring the `TaskDef`s passed to `.tasks` must cover all tasks selected by the `args` selector, which fails when we make multiple separate calls to `.tasks` each with the tasks related to an individual test class: https://github.com/com-lihaoyi/utest/blob/76e7fe5a72464ab44df3f829bd2b3e7699d522bf/utest/src/utest/runner/BaseRunner.scala#L67-L75 Arguably this is an issue in uTest for being too strict, but for now a workaround is to do all the work in a single call to `.tasks` with all test classes involved, and break it up into the per-test-class sublists after the fact Adjusted `example.scalalib.testing[1-test-suite]` to cover this case by having two test classes in the `foo` test suite
1 parent c7bdc33 commit 4b66c1d

File tree

9 files changed

+120
-30
lines changed

9 files changed

+120
-30
lines changed

example/javalib/testing/1-test-suite/build.mill

+2
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,5 @@ object bar extends JavaModule {
2727
)
2828
}
2929
}
30+
31+
//// SNIPPET:RUNSINGLE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package foo;
2+
3+
import static org.junit.Assert.assertTrue;
4+
import static org.mockito.Mockito.*;
5+
6+
import org.junit.Test;
7+
8+
public class FooMoreTests {
9+
10+
@Test
11+
public void hello() {
12+
String result = new Foo().hello();
13+
assertTrue(result.startsWith("Hello"));
14+
}
15+
16+
@Test
17+
public void world() {
18+
String result = new Foo().hello();
19+
assertTrue(result.endsWith("World"));
20+
}
21+
22+
@Test
23+
public void testMockito() {
24+
Foo mockFoo = mock(Foo.class);
25+
26+
when(mockFoo.hello()).thenReturn("Hello Mockito World");
27+
28+
String result = mockFoo.hello();
29+
30+
assertTrue(result.equals("Hello Mockito World"));
31+
verify(mockFoo).hello();
32+
}
33+
}

example/kotlinlib/testing/1-test-suite/build.mill

+2
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,5 @@ object bar extends KotlinModule {
4343
def kotlincOptions = super.kotlincOptions() ++ Seq("-jvm-target", "11")
4444
}
4545
}
46+
47+
//// SNIPPET:RUNSINGLE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package foo
2+
3+
import io.kotest.core.spec.style.FunSpec
4+
import io.kotest.matchers.shouldBe
5+
import io.kotest.matchers.string.shouldEndWith
6+
import io.kotest.matchers.string.shouldStartWith
7+
import org.mockito.kotlin.doReturn
8+
import org.mockito.kotlin.mock
9+
import org.mockito.kotlin.verify
10+
import org.mockito.kotlin.whenever
11+
12+
class FooMoreTests :
13+
FunSpec({
14+
15+
test("hello") {
16+
val result = Foo().hello()
17+
result shouldStartWith "Hello"
18+
}
19+
20+
test("world") {
21+
val result = Foo().hello()
22+
result shouldEndWith "World"
23+
}
24+
25+
test("mockito") {
26+
val mockFoo = mock<Foo>()
27+
28+
whenever(mockFoo.hello()) doReturn "Hello Mockito World"
29+
30+
val result = mockFoo.hello()
31+
32+
result shouldBe "Hello Mockito World"
33+
verify(mockFoo).hello()
34+
}
35+
})

example/package.mill

+1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ object `package` extends RootModule with Module {
134134
"kotest_filter_tests='hello' kotest_filter_specs='bar.BarTests' ./mill bar.test"
135135
)
136136
.replace("compiling 1 ... source...", "Compiling 1 ... source...")
137+
.replace("compiling 2 ... source...", "Compiling 2 ... source...")
137138
case _ => line
138139
}
139140
}

example/scalalib/testing/1-test-suite/build.mill

+13-1
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ object foo extends ScalaModule {
2828
compiling 1 ... source...
2929

3030
> mill foo.test.compile
31-
compiling 1 ... source...
31+
compiling 2 ... source...
3232

3333
> mill foo.test.testForked
3434
...foo.FooTests...hello ...
3535
...foo.FooTests...world ...
36+
...foo.FooMoreTests...hello ...
37+
...foo.FooMoreTests...world ...
3638

3739
> mill foo.test # same as above, `.test` is the default task for the `test` module
3840
...foo.FooTests...hello ...
@@ -131,6 +133,16 @@ object bar extends ScalaModule {
131133
// `foo.test` in the command line. e.g. {utest-github-url}[uTest]
132134
// lets you pass in a selector to decide which test to run, which in Mill would be:
133135

136+
//// SNIPPET:RUNSINGLE
137+
/** Usage
138+
139+
> mill foo.test foo.FooMoreTests
140+
...foo.FooMoreTests...hello ...
141+
142+
*/
143+
144+
//// SNIPPET:END
145+
134146
/** Usage
135147

136148
> mill bar.test bar.BarTests.hello
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package foo
2+
import utest._
3+
object FooMoreTests extends TestSuite {
4+
def tests = Tests {
5+
test("hello") {
6+
val result = Foo.hello()
7+
assert(result.startsWith("Hello"))
8+
result
9+
}
10+
test("world") {
11+
val result = Foo.hello()
12+
assert(result.endsWith("World"))
13+
result
14+
}
15+
}
16+
}

scalalib/test/resources/testrunner/doneMessageFailure/src/DoneMessageFailureFramework.scala

+1-8
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,18 @@ class DoneMessageFailureFramework extends Framework {
1414
def done() = "test failure done message"
1515
def remoteArgs() = Array.empty
1616
def tasks(taskDefs: Array[TaskDef]) = Array(new Task {
17-
def taskDef(): TaskDef = taskDefs.head
17+
def taskDef(): TaskDef = taskDefs.headOption.getOrElse(null)
1818
def execute(
1919
eventHandler: EventHandler,
2020
loggers: Array[Logger]
2121
): Array[Task] = {
2222
eventHandler.handle(new Event {
23-
2423
override def fullyQualifiedName(): String = "foo.bar"
25-
2624
override def fingerprint(): Fingerprint = new Fingerprint {}
27-
2825
override def selector(): Selector = new TestSelector("foo.bar")
29-
3026
override def status(): Status = Status.Failure
31-
3227
override def throwable(): OptionalThrowable = new OptionalThrowable()
33-
3428
override def duration(): Long = 0L
35-
3629
})
3730
Array.empty
3831
}

testrunner/src/mill/testrunner/TestRunnerUtils.scala

+17-21
Original file line numberDiff line numberDiff line change
@@ -120,28 +120,24 @@ import java.util.concurrent.atomic.AtomicBoolean
120120
val runner = framework.runner(args.toArray, Array[String](), cl)
121121
val testClasses = discoverTests(cl, framework, testClassfilePath)
122122

123-
val filteredTestClasses = testClasses.iterator.filter { case (cls, _) =>
124-
classFilter(cls)
125-
}.toArray
126-
127-
val tasksArr = // each test class can have multiple test tasks ==> array of test classes will have this signature
128-
if (filteredTestClasses.isEmpty) {
129-
// We still need to run runner's tasks on empty array
130-
Array(runner.tasks(Array.empty))
131-
} else {
132-
filteredTestClasses.map { case (cls, fingerprint) =>
133-
runner.tasks(
134-
Array(new TaskDef(
135-
cls.getName.stripSuffix("$"),
136-
fingerprint,
137-
false,
138-
Array(new SuiteSelector)
139-
))
140-
)
141-
}
142-
}
123+
val tasks = runner.tasks(
124+
for ((cls, fingerprint) <- testClasses.iterator.toArray if classFilter(cls))
125+
yield new TaskDef(
126+
cls.getName.stripSuffix("$"),
127+
fingerprint,
128+
false,
129+
Array(new SuiteSelector)
130+
)
131+
)
132+
133+
def nameOpt(t: Task) = Option(t.taskDef()).map(_.fullyQualifiedName())
134+
val groupedTasks = tasks
135+
.groupBy(nameOpt)
136+
.values
137+
.toArray
138+
.sortBy(_.headOption.map(nameOpt))
143139

144-
(runner, tasksArr)
140+
(runner, groupedTasks)
145141
}
146142

147143
private def executeTasks(

0 commit comments

Comments
 (0)