Skip to content

Commit 98d9211

Browse files
authored
Stabilize multi level build tests (#4252)
Break it out into separate suites for parallelism
1 parent e53ae3b commit 98d9211

File tree

1 file changed

+123
-107
lines changed

1 file changed

+123
-107
lines changed

integration/invalidation/multi-level-editing/src/MultiLevelBuildTests.scala

Lines changed: 123 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -13,122 +13,126 @@ import scala.util.matching.Regex
1313
// that the proper messages are reported, proper build classloaders are
1414
// re-used or invalidated, and the proper files end up getting watched
1515
// in all cases.
16-
object MultiLevelBuildTests extends UtestIntegrationTestSuite {
17-
val tests: Tests = Tests {
16+
trait MultiLevelBuildTests extends UtestIntegrationTestSuite {
17+
var savedClassLoaderIds: Seq[Option[Int]] = Nil
18+
val retryCount: Int = if (sys.env.contains("CI")) 2 else 0
19+
def runAssertSuccess(tester: IntegrationTester, expected: String): Unit = {
20+
val res = tester.eval("foo.run")
21+
assert(res.isSuccess == true)
22+
assert(res.out.contains(expected))
23+
}
24+
25+
def fooPaths(tester: IntegrationTester): Seq[os.Path] = Seq(
26+
tester.workspacePath / "foo/compile-resources",
27+
tester.workspacePath / "foo/resources",
28+
tester.workspacePath / "foo/src"
29+
)
30+
def buildPaths(tester: IntegrationTester): Seq[os.Path] = Seq(
31+
tester.workspacePath / "build.mill",
32+
tester.workspacePath / "mill-build/compile-resources",
33+
tester.workspacePath / "mill-build/resources",
34+
tester.workspacePath / "mill-build/src"
35+
)
36+
def buildPaths2(tester: IntegrationTester): Seq[os.Path] = Seq(
37+
tester.workspacePath / "mill-build/build.mill",
38+
tester.workspacePath / "mill-build/mill-build/compile-resources",
39+
tester.workspacePath / "mill-build/mill-build/resources",
40+
tester.workspacePath / "mill-build/mill-build/src"
41+
)
42+
def buildPaths3(tester: IntegrationTester): Seq[os.Path] = Seq(
43+
tester.workspacePath / "mill-build/mill-build/build.mill",
44+
tester.workspacePath / "mill-build/mill-build/mill-build/compile-resources",
45+
tester.workspacePath / "mill-build/mill-build/mill-build/resources",
46+
tester.workspacePath / "mill-build/mill-build/mill-build/src"
47+
)
48+
49+
def loadFrames(
50+
tester: IntegrationTester,
51+
n: Int
52+
): IndexedSeq[(RunnerState.Frame.Logged, os.Path)] = {
53+
for (depth <- Range(0, n))
54+
yield {
55+
val path =
56+
tester.workspacePath / "out" / Seq.fill(depth)(millBuild) / millRunnerState
57+
if (os.exists(path)) upickle.default.read[RunnerState.Frame.Logged](os.read(path)) -> path
58+
else RunnerState.Frame.Logged(Map(), Seq(), Seq(), None, Seq(), 0) -> path
59+
}
60+
}
1861

19-
def runAssertSuccess(tester: IntegrationTester, expected: String) = {
20-
val res = tester.eval("foo.run")
21-
assert(res.isSuccess == true)
22-
assert(res.out.contains(expected))
62+
/**
63+
* Verify that each level of the multi-level build ends upcausing the
64+
* appropriate files to get watched
65+
*/
66+
def checkWatchedFiles(tester: IntegrationTester, expected0: Seq[os.Path]*): Unit = {
67+
for ((expectedWatched0, (frame, path)) <- expected0.zip(loadFrames(tester, expected0.length))) {
68+
val frameWatched = frame
69+
.evalWatched
70+
.map(_.path)
71+
.filter(_.startsWith(tester.workspacePath))
72+
.filter(!_.segments.contains("mill-launcher"))
73+
.sorted
74+
75+
val expectedWatched = expectedWatched0.sorted
76+
assert(frameWatched == expectedWatched)
2377
}
78+
}
2479

25-
def fooPaths(tester: IntegrationTester) = Seq(
26-
tester.workspacePath / "foo/compile-resources",
27-
tester.workspacePath / "foo/resources",
28-
tester.workspacePath / "foo/src"
29-
)
30-
def buildPaths(tester: IntegrationTester) = Seq(
31-
tester.workspacePath / "build.mill",
32-
tester.workspacePath / "mill-build/compile-resources",
33-
tester.workspacePath / "mill-build/resources",
34-
tester.workspacePath / "mill-build/src"
35-
)
36-
def buildPaths2(tester: IntegrationTester) = Seq(
37-
tester.workspacePath / "mill-build/build.mill",
38-
tester.workspacePath / "mill-build/mill-build/compile-resources",
39-
tester.workspacePath / "mill-build/mill-build/resources",
40-
tester.workspacePath / "mill-build/mill-build/src"
41-
)
42-
def buildPaths3(tester: IntegrationTester) = Seq(
43-
tester.workspacePath / "mill-build/mill-build/build.mill",
44-
tester.workspacePath / "mill-build/mill-build/mill-build/compile-resources",
45-
tester.workspacePath / "mill-build/mill-build/mill-build/resources",
46-
tester.workspacePath / "mill-build/mill-build/mill-build/src"
80+
def evalCheckErr(tester: IntegrationTester, expectedSnippets: String*): Unit = {
81+
// Wipe out stale state files to make sure they don't get picked up when
82+
// Mill aborts early and fails to generate a new one
83+
os.walk(tester.workspacePath / "out").filter(_.last == "mill-runner-state.json").foreach(
84+
os.remove(_)
4785
)
4886

49-
def loadFrames(tester: IntegrationTester, n: Int) = {
50-
for (depth <- Range(0, n))
51-
yield {
52-
val path =
53-
tester.workspacePath / "out" / Seq.fill(depth)(millBuild) / millRunnerState
54-
if (os.exists(path)) upickle.default.read[RunnerState.Frame.Logged](os.read(path)) -> path
55-
else RunnerState.Frame.Logged(Map(), Seq(), Seq(), None, Seq(), 0) -> path
56-
}
87+
val res = tester.eval("foo.run")
88+
assert(res.isSuccess == false)
89+
// Prepend a "\n" to allow callsites to use "\n" to test for start of
90+
// line, even though the first line doesn't have a "\n" at the start
91+
val err = "```\n" + res.err + "\n```"
92+
for (expected <- expectedSnippets) {
93+
assert(err.contains(expected))
5794
}
95+
}
5896

59-
/**
60-
* Verify that each level of the multi-level build ends upcausing the
61-
* appropriate files to get watched
62-
*/
63-
def checkWatchedFiles(tester: IntegrationTester, expected0: Seq[os.Path]*) = {
64-
for (
65-
(expectedWatched0, (frame, path)) <- expected0.zip(loadFrames(tester, expected0.length))
66-
) {
67-
val frameWatched = frame
68-
.evalWatched
69-
.map(_.path)
70-
.filter(_.startsWith(tester.workspacePath))
71-
.filter(!_.segments.contains("mill-launcher"))
72-
.sorted
73-
74-
val expectedWatched = expectedWatched0.sorted
75-
assert(frameWatched == expectedWatched)
97+
/**
98+
* Check whether the classloaders of the nested meta-builds are changing as
99+
* expected. `true` means a new classloader was created, `false` means
100+
* the previous classloader was re-used, `null` means there is no
101+
* classloader at that level
102+
*/
103+
def checkChangedClassloaders(
104+
tester: IntegrationTester,
105+
expectedChanged0: java.lang.Boolean*
106+
): Unit = {
107+
val currentClassLoaderIds =
108+
for ((frame, path) <- loadFrames(tester, expectedChanged0.length))
109+
yield frame.classLoaderIdentity
110+
111+
val changed = currentClassLoaderIds
112+
.zipAll(savedClassLoaderIds, None, None)
113+
.map { case (cur, old) =>
114+
if (cur.isEmpty) null
115+
else cur != old
76116
}
77-
}
78117

79-
def evalCheckErr(tester: IntegrationTester, expectedSnippets: String*) = {
80-
// Wipe out stale state files to make sure they don't get picked up when
81-
// Mill aborts early and fails to generate a new one
82-
os.walk(tester.workspacePath / "out").filter(_.last == "mill-runner-state.json").foreach(
83-
os.remove(_)
84-
)
85-
86-
val res = tester.eval("foo.run")
87-
assert(res.isSuccess == false)
88-
// Prepend a "\n" to allow callsites to use "\n" to test for start of
89-
// line, even though the first line doesn't have a "\n" at the start
90-
val err = "```\n" + res.err + "\n```"
91-
for (expected <- expectedSnippets) {
92-
assert(err.contains(expected))
118+
val expectedChanged =
119+
if (clientServerMode) expectedChanged0
120+
else expectedChanged0.map {
121+
case java.lang.Boolean.FALSE => true
122+
case n => n
93123
}
94-
}
95124

96-
var savedClassLoaderIds = Seq.empty[Option[Int]]
97-
98-
/**
99-
* Check whether the classloaders of the nested meta-builds are changing as
100-
* expected. `true` means a new classloader was created, `false` means
101-
* the previous classloader was re-used, `null` means there is no
102-
* classloader at that level
103-
*/
104-
def checkChangedClassloaders(
105-
tester: IntegrationTester,
106-
expectedChanged0: java.lang.Boolean*
107-
) = {
108-
val currentClassLoaderIds =
109-
for ((frame, path) <- loadFrames(tester, expectedChanged0.length))
110-
yield frame.classLoaderIdentity
111-
112-
val changed = currentClassLoaderIds
113-
.zipAll(savedClassLoaderIds, None, None)
114-
.map { case (cur, old) =>
115-
if (cur.isEmpty) null
116-
else cur != old
117-
}
118-
119-
val expectedChanged =
120-
if (clientServerMode) expectedChanged0
121-
else expectedChanged0.map {
122-
case java.lang.Boolean.FALSE => true
123-
case n => n
124-
}
125-
126-
assert(changed == expectedChanged)
127-
128-
savedClassLoaderIds = currentClassLoaderIds
129-
}
125+
assert(changed == expectedChanged)
126+
127+
savedClassLoaderIds = currentClassLoaderIds
128+
}
129+
130+
}
131+
132+
object MultiLevelBuildTestsValidEdits extends MultiLevelBuildTests {
133+
val tests: Tests = Tests {
134+
savedClassLoaderIds = Seq.empty[Option[Int]]
130135

131-
val retryCount = if (sys.env.contains("CI")) 2 else 0
132136
test("validEdits") - retry(retryCount) {
133137
integrationTest { tester =>
134138
import tester._
@@ -246,7 +250,11 @@ object MultiLevelBuildTests extends UtestIntegrationTestSuite {
246250
checkChangedClassloaders(tester, null, false, false, false)
247251
}
248252
}
249-
253+
}
254+
}
255+
object MultiLevelBuildTestsParseErrorEdits extends MultiLevelBuildTests {
256+
val tests: Tests = Tests {
257+
savedClassLoaderIds = Seq.empty[Option[Int]]
250258
test("parseErrorEdits") - retry(retryCount) {
251259
integrationTest { tester =>
252260
import tester._
@@ -315,7 +323,11 @@ object MultiLevelBuildTests extends UtestIntegrationTestSuite {
315323
checkChangedClassloaders(tester, null, true, true, true)
316324
}
317325
}
318-
326+
}
327+
}
328+
object MultiLevelBuildTestsCompileErrorEdits extends MultiLevelBuildTests {
329+
val tests: Tests = Tests {
330+
savedClassLoaderIds = Seq.empty[Option[Int]]
319331
test("compileErrorEdits") - retry(retryCount) {
320332
integrationTest { tester =>
321333
import tester._
@@ -399,7 +411,11 @@ object MultiLevelBuildTests extends UtestIntegrationTestSuite {
399411
checkChangedClassloaders(tester, null, true, false, false)
400412
}
401413
}
402-
414+
}
415+
}
416+
object MultiLevelBuildTestsRuntimeErrorEdits extends MultiLevelBuildTests {
417+
val tests: Tests = Tests {
418+
savedClassLoaderIds = Seq.empty[Option[Int]]
403419
test("runtimeErrorEdits") - retry(retryCount) {
404420
integrationTest { tester =>
405421
import tester._

0 commit comments

Comments
 (0)