Skip to content
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

Merged
merged 11 commits into from
May 25, 2020
Merged

Dump shards #794

merged 11 commits into from
May 25, 2020

Conversation

bootstraponline
Copy link
Contributor

@bootstraponline bootstraponline commented May 16, 2020

Fixes #791

Checklist

  • Documented
  • Unit tested
  • release_notes.md updated

pawelpasterz
pawelpasterz previously approved these changes May 16, 2020
Copy link
Contributor

@pawelpasterz pawelpasterz left a 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(", ")}")
Copy link
Contributor

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?

@bootstraponline
Copy link
Contributor Author

There's a lot of duplication with getTestShardChunks as well, I should probably refactor this.

I will have time Monday to address the review comments and add a unit test. Thanks for the review. 🙂

@runningcode
Copy link
Contributor

Looks great! Thanks.

@@ -27,6 +27,23 @@ object AndroidTestShard {
listOf(filteredTests.map(FlankTestMethod::testName))
}

fun getAllLocalTestShardChunks(args: AndroidArgs): ShardChunks {
Copy link
Contributor

@jan-goral jan-goral May 18, 2020

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.

@codecov-commenter
Copy link

codecov-commenter commented May 19, 2020

Codecov Report

Merging #794 into master will decrease coverage by 0.18%.
The diff coverage is 75.00%.

Impacted file tree graph

@@             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     

Copy link
Contributor

@pawelpasterz pawelpasterz left a 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:

  1. wrong result (or correct me if I'm wrong)
  2. 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 {
Copy link
Contributor

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?

Copy link
Contributor

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 :)

Copy link
Contributor Author

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()
Copy link
Contributor

@pawelpasterz pawelpasterz May 19, 2020

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.

Copy link
Contributor Author

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.

@bootstraponline
Copy link
Contributor Author

bootstraponline commented May 20, 2020

I think for this PR, we probably want an output format in android_shards.json like this:

{
  "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.

@pawelpasterz
Copy link
Contributor

@bootstraponline
Looks good to me!

@jan-goral jan-goral self-assigned this May 21, 2020
@jan-goral
Copy link
Contributor

@bootstraponline

{
  "matrix-0": {
    "app": "/Users/janek/projects/flank-project/flank/test_runner/src/test/kotlin/ftl/fixtures/tmp/apk/app-debug.apk",
    "test": "/var/folders/p5/ll89kbn91t37gz9ry9kwxbh40000gp/T/tmp806579008668219334.tmp",
    "shards": {
      "shard-0": [
        "class com.example.test_app.InstrumentedTest#test"
      ]
    }
  },
  "matrix-1": {
    "app": "/Users/janek/projects/flank-project/flank/test_runner/src/test/kotlin/ftl/fixtures/tmp/apk/app-debug.apk",
    "test": "/var/folders/p5/ll89kbn91t37gz9ry9kwxbh40000gp/T/tmp5261068495618932935.tmp",
    "shards": {
      "shard-0": [
        "class com.example.test_app.InstrumentedTest#test1",
        "class com.example.test_app.InstrumentedTest#test2"
      ],
      "shard-1": [
        "class com.example.test_app.InstrumentedTest#test0",
        "class com.example.test_app.bar.BarInstrumentedTest#testBar",
        "class com.example.test_app.foo.FooInstrumentedTest#testFoo"
      ]
    }
  }
}

// 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"])
Copy link
Contributor Author

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?

Copy link
Contributor

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"])
Copy link
Contributor Author

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 ?

@bootstraponline
Copy link
Contributor Author

gcloud:
  app: ../test_app/apks/app-debug.apk
  test: ./src/test/kotlin/ftl/fixtures/tmp/apk/app-debug-androidTest_155.apk
  num-uniform-shards: 50 

produces:

{
  "matrix-0": {
    "app": "/Users/mac/code/flank/test_app/apks/app-debug.apk",
    "test": "/Users/mac/code/flank/test_runner/src/test/kotlin/ftl/fixtures/tmp/apk/app-debug-androidTest_155.apk",
    "shards": {
      "shard-0": [ ... ]

I think we should update --dump-shards to take into account num-uniform-shards. The idea is that given a config, we can preview the matrix/shard layout before running.

@bootstraponline
Copy link
Contributor Author

I tested with max-test-shards and additional-app-test-apks. The feature looks great!

@bootstraponline
Copy link
Contributor Author

The help message is a bit mixed up 🙂

$ flank android run --help
--dump-shards Dumps the shards to ios_shards.json for debugging

pawelpasterz
pawelpasterz previously approved these changes May 23, 2020
testFilter = TestFilters.fromTestTargets(args.testTargets)
).mapValues { (_, testMethods: List<FlankTestMethod>) ->
when {
testMethods.isEmpty() -> emptyList<List<String>>().also { printNoTests(testApks) }
Copy link
Contributor

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() }

Copy link
Contributor

@jan-goral jan-goral May 23, 2020

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 ->
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor

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(),
Copy link
Contributor

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

Copy link
Contributor

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 ?

Copy link
Contributor Author

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

@bootstraponline
Copy link
Contributor Author

The code looks great!

@pawelpasterz pawelpasterz merged commit 2c328e1 into master May 25, 2020
@pawelpasterz pawelpasterz deleted the dump_shards branch May 25, 2020 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Does --dump-shards take in to account additional-app-test-apks?
5 participants