-
-
Notifications
You must be signed in to change notification settings - Fork 393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add test runner indicators #4737
Conversation
@internal private[TestRunnerUtils] final class TestEventSummary(outputFileOpt: Option[os.Path]) | ||
extends AtomicBoolean(true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class wrapper and inheritance is kind of weird. Let's convert them to just static helper methods and pass in outputFileOpt
and the AtomicBoolean
as arguments where necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a trick to better the memory layout of object, I guess it's kind of overkill here
val (success, failure) = upickle.default.read[(Long, Long)](os.read.stream(outputFile)) | ||
val (newSuccess, newFailure) = | ||
if (isSuccess) (success + 1, failure) else (success, failure + 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This workflow to both read and write to the file on disk is unnecessarily complicated. Let's just keep the success/failure count as in-memory Long
(or AtomicLong
) and have the file be write-only purely for the purpose of passing the information to the parent process
// help gc | ||
workerStatusMap.clear() | ||
workerStatsSet.clear() | ||
testClassTimeMap.clear() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be unnecessary, let's skip it
val result = callTestRunnerSubprocess( | ||
base, | ||
Right((startingTestClass, testClassQueueFolder, claimFolder)) | ||
) | ||
workerStatusMap.remove(claimLog) | ||
// We don't remove workerStatsSet entry, as we still need them for calculation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can elaborate a bit more ...for calculation of total success/failure counts
@HollandDM Looks pretty good, some small changes requested. Please also update the PR description with a summary of your implementation strategy |
2ce3ad7
to
0e2ce69
Compare
Updated to adrees the feedbacks |
event.status match { | ||
case Status.Error => taskStatus.set(false) | ||
case Status.Failure => taskStatus.set(false) | ||
case _ => taskStatus.compareAndSet(true, true) // consider success as a default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can leave out this compareAndSet
; this branch of the match
being a no-op would be sufficient
@@ -225,6 +225,8 @@ private final class TestModuleUtil( | |||
)(implicit ctx: mill.api.Ctx) = { | |||
|
|||
val workerStatusMap = new java.util.concurrent.ConcurrentHashMap[os.Path, String => Unit]() | |||
val workerStatsSet = new java.util.concurrent.ConcurrentHashMap[os.Path, Unit]() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe call this workerResultsSet
? Stats
and Status
sounds too similar
val suffix = ((now - last) / 1000).toInt match { | ||
case 0 => "" | ||
case n => s" ${n}s" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move PromptLoggerUtil.renderSecondsSuffix
to mill.internal.Util
so we can re-use it here
e704d40
to
88d8467
Compare
Updated again. After the ticket is good to merge, I'll squash them back into 1 commit, I think it'll be better |
I think this looks good for |
No need to worry about squashing your commits, we'll squash it during the merge using github's squash-and-merge button |
totalFailure += failure | ||
} | ||
ctx.log.ticker( | ||
s"${totalSuccess + totalFailure}/${filteredClassCount} completed, ${totalFailure} failures" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's only print n failures
for n > 0
, since that's what we do in the main prompt for failed tasks
053cd80
to
a2aab21
Compare
Updated, I added progress indicator (complete/failure) to default run as well. |
a2aab21
to
0810718
Compare
try { | ||
// Periodically check the result log file and tick the relevant infos | ||
executor.scheduleWithFixedDelay( | ||
() => { | ||
val (totalSuccess, totalFailure) = TestModuleUtil.calculateTestProgress(workerResultSet) | ||
ctx.log.ticker(s"${totalSuccess + totalFailure}/${filteredClassCount} completed${ | ||
if totalFailure > 0 then s", ${totalFailure} failures" else "" | ||
}") | ||
}, | ||
0, | ||
20, | ||
java.util.concurrent.TimeUnit.MILLISECONDS | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's consolidate the whole val workerResultSet
, newScheduledThreadPool
, scheduleWithFixedDelay
, finally executor.shutdown
logic into a single
def withTestProgressTickerThread(updateWorkerStatuses: Boolean)(block: => T): T
That should help DRY up the duplicate code, and also shorten some of these def runTestQueueScheduler
/def runTestDefault
methods which are getting rather long
@@ -203,15 +211,34 @@ import java.io.PrintStream | |||
} | |||
|
|||
def runTasks( | |||
tasks: Seq[Task], | |||
tasksSeq: Seq[Seq[Task]], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for this change in signature from Seq[Task]
to Seq[Seq[Task]]
? I've been looking at the code and haven't managed to figure out the intention
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess each element of the outer Seq
represents one test class, and each inner Seq
represents the tasks for that test class? If so, probably worth a comment somewhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'm adding the comments in the backport PR, will add it in this as well. But it's meaning is exactly what you've said
Some last nitpicks. Tried it out locally and works great. Turned on CI, once it's green it should be good to merge |
The |
09fc0c4
to
0e1dfa3
Compare
updated, the test failing is due to the changes of |
val resultPath = base / s"result.log" | ||
os.write.over(resultPath, upickle.default.write((0L, 0L))) | ||
workerResultSet.put(resultPath, ()) | ||
|
||
val result = callTestRunnerSubprocess( | ||
base, | ||
resultPath, | ||
Right((startingTestClass, testClassQueueFolder, claimFolder)) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we consolidate this logic to use runTestRunnerSubprocess
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
runTestRunnerSubprocess
's are quite different between queueRunner
and defaultRunner
, so I have to create 2 versions for it, each in the corresponding runners function.
I'm also thinking of a way to further clean up the logic, if we can generalize both runners to just produce blueprint for subprocess, and do the spawn + logging in a shared function, then the code should be clearer. But that required some refactoring.
If you want, I can try to go that way and see if we can do any better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, I think for now let's just leave it as is then
back port of #4737 --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Thanks @HollandDM , i will send the bounty using your existing transfer details |
Thanks @lihaoyi, lets use that as my transfer detail |
/claim #4735
This PR add some calculation to add test progress & failure indicator for each test runner while
testParallelism
is set totrue
To add progress indicators, this pull request introduces a new file within each worker's claim folder. This file is overwritten every time
runClaimedTestClass
is called. The parent process will checks these files in each worker, accumulates their contents, and outputs them via aticker
.For test timing, this pull request uses a separate map, similar to
workerStatusMap
, to store the starting time. The time calculation is then straightforward, and the result is output via aticker
alongside the test class name.