Dump shards#794
Conversation
pawelpasterz
left a comment
There was a problem hiding this comment.
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.
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. |
| listOf(filteredTests.map(FlankTestMethod::testName)) | ||
| } | ||
|
|
||
| fun getAllLocalTestShardChunks(args: AndroidArgs): ShardChunks { |
There was a problem hiding this comment.
Is Local meaning that this method doesn't accept test apks by gcs paths, right?
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 |
pawelpasterz
left a comment
There was a problem hiding this comment.
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.
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.
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.
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.
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.
Thanks. I think you're right.
I think there are two different use cases:
- Serialize the shards to
android_shards.jsonfor 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.
IosRunCommand.kt references ANDROID_SHARD_FILE, is that correct?
| // 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.
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.
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.
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.
How about more functional styling here, WDYT?
There was a problem hiding this comment.
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.
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.
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.
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.
I think concurrent downloads would be nice
|
The code looks great! |
Fixes #791
Checklist