-
Notifications
You must be signed in to change notification settings - Fork 119
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
Dump shards #794
Dump shards #794
Conversation
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 like the solution. One thing to mention, I think it would be nice to cover that case with unit tests.
|
||
val filteredTests = testApks.map { getTestMethods(args, it) }.flatten() | ||
|
||
if (filteredTests.isEmpty()) println("${FtlConstants.indent}No tests for ${testApks.joinToString(", ")}") |
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.
How about returning with empty list here?
There's a lot of duplication with I will have time Monday to address the review comments and add a unit test. Thanks for the review. 🙂 |
Looks great! Thanks. |
@@ -27,6 +27,23 @@ object AndroidTestShard { | |||
listOf(filteredTests.map(FlankTestMethod::testName)) | |||
} | |||
|
|||
fun getAllLocalTestShardChunks(args: AndroidArgs): ShardChunks { |
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.
Is Local
meaning that this method doesn't accept test apks by gcs paths, right?
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.
Correct.
2044e40
to
d180af0
Compare
Codecov Report
@@ Coverage Diff @@
## master #794 +/- ##
============================================
- Coverage 78.90% 78.71% -0.19%
Complexity 696 696
============================================
Files 137 143 +6
Lines 2891 2950 +59
Branches 417 423 +6
============================================
+ Hits 2281 2322 +41
- Misses 342 354 +12
- Partials 268 274 +6 |
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.
There are two things that are concerns to me:
- wrong result (or correct me if I'm wrong)
- we've lost some clarity of code. I like refactor and reusability of code but there are some places that are unclear for me (at first glance)
|
||
val filteredTests = getTestMethods(args, testLocalApk) | ||
fun getTestShardChunks(args: AndroidArgs, testApks: List<String> = listOf()): ShardChunks { |
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.
The actual need to pass list here is only when user use --dump-shards
option. I am not convinced with this solution for normal (test run) flow, then we need to create list with one element (for every test pair):
AndroidTestShards L41
args.resolveApks().forEach { apks: ResolvedApks ->
val testShards = apks.test?.let { test ->
AndroidTestShard.getTestShardChunks(args, listOf(test))
}
I think wrapping one element into list is a bit code smell, what do you think?
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 just overload getTestShardChunks
with
fun getTestShardChunks(args: AndroidArgs, testApk: String) = getTestShardChunks(args, listOf(testApk))
to reduce changes in other files :)
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 wrapping one element into list is a bit code smell, what do you think?
I agree
val filteredTests = getTestMethods(args, testLocalApk) | ||
fun getTestShardChunks(args: AndroidArgs, testApks: List<String> = listOf()): ShardChunks { | ||
val resolvedApks = getTestApks(args, testApks) | ||
val filteredTests = resolvedApks.map { getTestMethods(args, it) }.flatten() |
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 this is producing wrong results. Here we have list of all tests from all test pairs. With normal flow we have new matrix for each test pair and inside that matrix flank divides tests into shards. With this solution we have all tests in one list (from all test pairs) and then flank make shards, so we basically neglect how tests are divided among matrices. I may be wrong, but this is not result we would like to have.
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.
Thanks. I think you're right.
I think there are two different use cases:
- Serialize the shards to
android_shards.json
for debugging. - Given a single test apk, generate shards.
Maybe these should be different methods after all.
I think for this PR, we probably want an output format in {
"matrix-1": {
"app": "foo.apk",
"test": "bar.apk",
"shards": {
"shard-1": ["class com.example.app.ExampleUiTest#testPasses"]
}
}
} Then it will be very clear to the user how many matrices will be created, and what shards will be used. The goal is to provide as much information as possible to debug problems related to the sharding algorithm. |
@bootstraponline |
|
May replace !! with .orEmpty() in the future
// Flank debug | ||
|
||
@Option(names = ["--dump-shards"], description = ["Dumps the shards to $shardFile for debugging"]) | ||
@Option(names = ["--dump-shards"], description = ["Dumps the shards to $ANDROID_SHARD_FILE for debugging"]) |
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.
IosRunCommand.kt
references ANDROID_SHARD_FILE
, is that correct?
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.
shame on me...
// Flank debug | ||
|
||
@Option(names = ["--dump-shards"], description = ["Dumps the shards to $shardFile for debugging"]) | ||
@Option(names = ["--dump-shards"], description = ["Dumps the shards to $IOS_SHARD_FILE for debugging"]) |
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.
AndroidRunCommand.kt
references $IOS_SHARD_FILE
?
produces:
I think we should update |
I tested with |
The help message is a bit mixed up 🙂
|
testFilter = TestFilters.fromTestTargets(args.testTargets) | ||
).mapValues { (_, testMethods: List<FlankTestMethod>) -> | ||
when { | ||
testMethods.isEmpty() -> emptyList<List<String>>().also { printNoTests(testApks) } |
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 also
should be used if we want to perform some additional actions on caller object which is not true here. I would prefer simple block code. We can write it in one line if you want
testMethods.isEmpty() -> { printNoTests(testApks); emptyList() }
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.
IMO with also
looks better because emptyList
is important here but print is only additional side operation but I agree that avoiding unnecessary calls is a good idea 👍
testFilter: TestFilter | ||
): Map<InstrumentationTestApk, List<FlankTestMethod>> = | ||
testApks.associateWith { testApk -> | ||
DexParser.findTestMethods(testApk.test.local).asSequence().distinct().filter(testFilter.shouldRun).map { testMethod -> |
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.
How about more functional styling here, WDYT?
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.
There are only function calls, no value assignments, no ordinary loops, even no conditions so I have no idea how to do it more functional :). Pls explain me by example.
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.
Well my comment was just cosmetic one. Here we have invocation chain in one line, I was thinking about splitting it to new line each. So as I said 100% cosmetic
testApks: List<InstrumentationTestApk> | ||
): Map<InstrumentationTestApk, ShardChunks> = | ||
getFlankTestMethods( | ||
testApks = testApks.download(), |
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.
It would be great to have concurrent download but I guess it's for another PR :)
cc @bootstraponline
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 was thinking about concurrent download, but decided to leave as is due to additional changes in tests. But I can add it easily. @bootstraponline ?
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 concurrent downloads would be nice
The code looks great! |
Fixes #791
Checklist