@@ -21,6 +21,7 @@ import mill.util.Jvm
2121import os .Path
2222import scala .util .Try
2323import scala .collection .mutable
24+
2425/**
2526 * Core configuration required to compile a single Java compilation target
2627 */
@@ -95,116 +96,122 @@ trait JavaModule
9596 }
9697 }
9798
98- def testQuick (args : String * ): Command [(String , Seq [TestResult ])] = Task .Command (persistent = true ) {
99- val quicktestFailedClassesLog = Task .dest / " quickTestFailedClasses.log"
100- val (analysisFolder, previousAnalysisFolderOpt) = callGraphAnalysis()
101- val testClasses = testForkGrouping()
102- val quickTestClassLists = previousAnalysisFolderOpt.fold {
103- // no previous analysis folder, all classes need to be tested
104- testClasses
105- } { _ =>
106- // get spanning invalidation tree content
107- val spanningInvalidationTreeObj = Try {
108- upickle.default.read[ujson.Obj ](os.read.stream(analysisFolder / " spanningInvalidationTree.json" ))
109- }.getOrElse(ujson.Obj ())
110-
111- // we cannot parse the json back to its concrete type, we only know that all of them are
112- // encoded with class name as prefix.
113- // so we can do a prefix checking to get the affected class.
114- // naive implementation won't work well as it has loop through all `testClasses` and check prefix
115- // for all of them, so we employ a prefix trie to speed up the prefix checking.
116-
117- val jsonValueQueue = mutable.ArrayDeque [(String , ujson.Value )]()
118- val prefixTrie = new PrefixTrie [String ]()
119- jsonValueQueue.appendAll(spanningInvalidationTreeObj.value)
120- while (jsonValueQueue.nonEmpty) {
121- val (nodeStr, value) = jsonValueQueue.removeHead()
122- val parts = nodeStr
123- .stripPrefix(" def " )
124- .stripPrefix(" call " )
125- .stripPrefix(" external " )
126- .split(" [\\ .\\ $\\ !]" )
127- .map(_.trim())
128- .toSeq
129- prefixTrie.insert(parts)
130- value match {
131- case ujson.Obj (fieldMap) => jsonValueQueue.appendAll(fieldMap)
132- case _ => ()
99+ def testQuick (args : String * ): Command [(String , Seq [TestResult ])] =
100+ Task .Command (persistent = true ) {
101+ val quicktestFailedClassesLog = Task .dest / " quickTestFailedClasses.log"
102+ val (analysisFolder, previousAnalysisFolderOpt) = callGraphAnalysis()
103+ val testClasses = testForkGrouping()
104+ val quickTestClassLists = previousAnalysisFolderOpt.fold {
105+ // no previous analysis folder, all classes need to be tested
106+ testClasses
107+ } { _ =>
108+ // get spanning invalidation tree content
109+ val spanningInvalidationTreeObj = Try {
110+ upickle.default.read[ujson.Obj ](
111+ os.read.stream(analysisFolder / " spanningInvalidationTree.json" )
112+ )
113+ }.getOrElse(ujson.Obj ())
114+
115+ // we cannot parse the json back to its concrete type, we only know that all of them are
116+ // encoded with class name as prefix.
117+ // so we can do a prefix checking to get the affected class.
118+ // naive implementation won't work well as it has loop through all `testClasses` and check prefix
119+ // for all of them, so we employ a prefix trie to speed up the prefix checking.
120+
121+ val jsonValueQueue = mutable.ArrayDeque [(String , ujson.Value )]()
122+ val prefixTrie = new PrefixTrie [String ]()
123+ jsonValueQueue.appendAll(spanningInvalidationTreeObj.value)
124+ while (jsonValueQueue.nonEmpty) {
125+ val (nodeStr, value) = jsonValueQueue.removeHead()
126+ val parts = nodeStr
127+ .stripPrefix(" def " )
128+ .stripPrefix(" call " )
129+ .stripPrefix(" external " )
130+ .split(" [\\ .\\ $\\ !]" )
131+ .map(_.trim())
132+ .toSeq
133+ prefixTrie.insert(parts)
134+ value match {
135+ case ujson.Obj (fieldMap) => jsonValueQueue.appendAll(fieldMap)
136+ case _ => ()
137+ }
133138 }
139+
140+ val failedTestClasses =
141+ if (! os.exists(quicktestFailedClassesLog)) {
142+ Set .empty[String ]
143+ } else {
144+ Try {
145+ upickle.default.read[Seq [String ]](os.read.stream(quicktestFailedClassesLog))
146+ }.getOrElse(Seq .empty[String ]).toSet
147+ }
148+
149+ val result = testClasses.map(_.filter { testClassName =>
150+ failedTestClasses.contains(testClassName) || {
151+ val parts = testClassName
152+ .split(" \\ ." )
153+ .map(_.trim())
154+ .toSeq
155+ prefixTrie.contains(parts)
156+ }
157+ }).filter(_.nonEmpty)
158+ // help gc
159+ prefixTrie.clear()
160+ result
134161 }
135162
136- val failedTestClasses =
137- if (! os.exists(quicktestFailedClassesLog)) {
138- Set .empty[String ]
139- } else {
140- Try {
141- upickle.default.read[Seq [String ]](os.read.stream(quicktestFailedClassesLog))
142- }.getOrElse(Seq .empty[String ]).toSet
143- }
163+ // Clean up the directory for test runners
164+ os.walk(Task .dest).foreach { subPath => os.remove.all(subPath) }
165+
166+ val quickTestReportXml = testReportXml()
167+
168+ val testModuleUtil = new TestModuleUtil (
169+ testUseArgsFile(),
170+ forkArgs(),
171+ Seq .empty,
172+ zincWorker().scalalibClasspath(),
173+ resources(),
174+ testFramework(),
175+ runClasspath(),
176+ testClasspath(),
177+ args.toSeq,
178+ quickTestClassLists,
179+ zincWorker().testrunnerEntrypointClasspath(),
180+ forkEnv(),
181+ testSandboxWorkingDir(),
182+ forkWorkingDir(),
183+ quickTestReportXml,
184+ zincWorker().javaHome().map(_.path),
185+ testParallelism()
186+ )
144187
145- val result = testClasses.map(_.filter { testClassName =>
146- failedTestClasses.contains(testClassName) || {
147- val parts = testClassName
148- .split(" \\ ." )
149- .map(_.trim())
150- .toSeq
151- prefixTrie.contains(parts)
152- }
153- }).filter(_.nonEmpty)
154- // help gc
155- prefixTrie.clear()
156- result
157- }
188+ val results = testModuleUtil.runTests()
189+
190+ val badTestClasses = results match {
191+ case Result .Failure (_) =>
192+ // Consider all quick testing classes as failed
193+ quickTestClassLists.flatten
194+ case Result .Success ((_, results)) =>
195+ // Get all test classes that failed
196+ results
197+ .filter(testResult => Set (" Error" , " Failure" ).contains(testResult.status))
198+ .map(_.fullyQualifiedName)
199+ }
158200
159- // Clean up the directory for test runners
160- os.walk(Task .dest).foreach { subPath => os.remove.all(subPath) }
161-
162- val quickTestReportXml = testReportXml()
163-
164- val testModuleUtil = new TestModuleUtil (
165- testUseArgsFile(),
166- forkArgs(),
167- Seq .empty,
168- zincWorker().scalalibClasspath(),
169- resources(),
170- testFramework(),
171- runClasspath(),
172- testClasspath(),
173- args.toSeq,
174- quickTestClassLists,
175- zincWorker().testrunnerEntrypointClasspath(),
176- forkEnv(),
177- testSandboxWorkingDir(),
178- forkWorkingDir(),
179- quickTestReportXml,
180- zincWorker().javaHome().map(_.path),
181- testParallelism()
182- )
201+ os.write.over(
202+ quicktestFailedClassesLog,
203+ upickle.default.write[Seq [String ]](badTestClasses.distinct)
204+ )
183205
184- val results = testModuleUtil.runTests()
185-
186- val badTestClasses = results match {
187- case Result .Failure (_) =>
188- // Consider all quick testing classes as failed
189- quickTestClassLists.flatten
190- case Result .Success ((_, results)) =>
191- // Get all test classes that failed
192- results
193- .filter(testResult => Set (" Error" , " Failure" ).contains(testResult.status))
194- .map(_.fullyQualifiedName)
195- }
196-
197- os.write.over(quicktestFailedClassesLog, upickle.default.write[Seq [String ]](badTestClasses.distinct))
198-
199- results match {
200- case Result .Failure (errMsg) => Result .Failure (errMsg)
201- case Result .Success ((doneMsg, results)) =>
202- try TestModule .handleResults(doneMsg, results, Task .ctx(), quickTestReportXml)
203- catch {
204- case e : Throwable => Result .Failure (" Test reporting failed: " + e)
205- }
206+ results match {
207+ case Result .Failure (errMsg) => Result .Failure (errMsg)
208+ case Result .Success ((doneMsg, results)) =>
209+ try TestModule .handleResults(doneMsg, results, Task .ctx(), quickTestReportXml)
210+ catch {
211+ case e : Throwable => Result .Failure (" Test reporting failed: " + e)
212+ }
213+ }
206214 }
207- }
208215 }
209216
210217 def defaultCommandName (): String = " run"
@@ -1496,8 +1503,8 @@ trait JavaModule
14961503
14971504 @ internal
14981505 protected def callGraphAnalysisIgnoreCalls (
1499- callSiteOpt : Option [mill.codesig.JvmModel .MethodDef ],
1500- calledSig : mill.codesig.JvmModel .MethodSig
1506+ callSiteOpt : Option [mill.codesig.JvmModel .MethodDef ],
1507+ calledSig : mill.codesig.JvmModel .MethodSig
15011508 ): Boolean = {
15021509 // We can ignore all calls to methods that look like Targets when traversing
15031510 // the call graph. We can do this because we assume `def` Targets are pure,
@@ -1534,10 +1541,10 @@ trait JavaModule
15341541 isSimpleTarget(callSiteSig.desc) && calledSig.name.contains(" $anonfun" )
15351542 )
15361543 }
1537-
1544+
15381545 isSimpleTarget(calledSig.desc) && ! isForwarderCallsiteOrLambda
15391546 }
1540-
1547+
15411548 // Return the directory containing the current call graph analysis results, and previous one too if it exists
15421549 def callGraphAnalysis : T [(Path , Option [Path ])] = Task (persistent = true ) {
15431550 os.remove.all(Task .dest / " previous" )
@@ -1548,7 +1555,8 @@ trait JavaModule
15481555 .compute(
15491556 classFiles = os.walk(compile().classes.path).filter(_.ext == " class" ),
15501557 upstreamClasspath = compileClasspath().toSeq.map(_.path),
1551- ignoreCall = (callSiteOpt, calledSig) => callGraphAnalysisIgnoreCalls(callSiteOpt, calledSig),
1558+ ignoreCall =
1559+ (callSiteOpt, calledSig) => callGraphAnalysisIgnoreCalls(callSiteOpt, calledSig),
15521560 logger = new mill.codesig.Logger (
15531561 Task .dest / " current" ,
15541562 Option .when(debugEnabled)(Task .dest / " current" )
@@ -1560,7 +1568,7 @@ trait JavaModule
15601568 )
15611569 )
15621570 )
1563-
1571+
15641572 (Task .dest / " current" , Option .when(os.exists(Task .dest / " previous" ))(Task .dest / " previous" ))
15651573 }
15661574}
0 commit comments