Skip to content

Commit

Permalink
Fix test sharding and improve test coverage (#346)
Browse files Browse the repository at this point in the history
  • Loading branch information
bootstraponline authored Oct 11, 2018
1 parent 86a530f commit 7bb7531
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 37 deletions.
6 changes: 2 additions & 4 deletions test_runner/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2560m
kotlin.code.style=official
org.gradle.parallel=true

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
7 changes: 3 additions & 4 deletions test_runner/src/main/kotlin/ftl/args/AndroidArgs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,17 @@ class AndroidArgs(
val filteredTests = getTestMethods(testLocalApk)

testShardChunks = calculateShards(
testTargets,
filteredTests,
testTargetsAlwaysRun,
testShards
)
}

private fun getTestMethods(testLocalApk: String): List<String> {
val validTestMethods = DexParser.findTestMethods(testLocalApk)
require(validTestMethods.isNotEmpty()) { Utils.fatalError("Test APK has no tests") }
val allTestMethods = DexParser.findTestMethods(testLocalApk)
require(allTestMethods.isNotEmpty()) { Utils.fatalError("Test APK has no tests") }
val testFilter = TestFilters.fromTestTargets(testTargets)
val filteredTests = validTestMethods
val filteredTests = allTestMethods
.asSequence()
.filter(testFilter.shouldRun)
.map(TestMethod::testName)
Expand Down
14 changes: 6 additions & 8 deletions test_runner/src/main/kotlin/ftl/args/ArgsHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,11 @@ object ArgsHelper {
}

fun calculateShards(
testTargets: Collection<String>,
validTestNames: Collection<String>,
testMethodsToShard: Collection<String>,
testMethodsAlwaysRun: Collection<String>,
testShards: Int
): List<List<String>> {
val testShardMethods = if (testTargets.isNotEmpty()) {
testTargets
} else {
validTestNames
}.distinct().toMutableList()
val testShardMethods = testMethodsToShard.distinct().toMutableList()
testShardMethods.removeAll(testMethodsAlwaysRun)

val oneTestPerChunk = testShards == -1
Expand All @@ -87,7 +82,10 @@ object ArgsHelper {
chunkSize = 1
}

val testShardChunks = testShardMethods.chunked(chunkSize).map { testMethodsAlwaysRun + it }
val testShardChunks = testShardMethods.asSequence()
.chunked(chunkSize)
.map { testMethodsAlwaysRun + it }
.toList()

// Ensure we don't create more VMs than requested. VM count per run should be <= testShards
if (!oneTestPerChunk && testShardChunks.size > testShards) {
Expand Down
12 changes: 8 additions & 4 deletions test_runner/src/main/kotlin/ftl/args/IosArgs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,16 @@ class IosArgs(

val validTestMethods = Xctestrun.findTestNames(xctestrunFile)
validateTestMethods(testTargets, validTestMethods, "xctest binary")
val testsToShard = if (testTargets.isEmpty()) {
validTestMethods
} else {
testTargets
}

testShardChunks = ArgsHelper.calculateShards(
testTargets,
validTestMethods,
testTargetsAlwaysRun,
testShards
testMethodsToShard = testsToShard,
testMethodsAlwaysRun = testTargetsAlwaysRun,
testShards = testShards
)
}

Expand Down
31 changes: 22 additions & 9 deletions test_runner/src/test/kotlin/ftl/args/AndroidArgsFileTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ import org.junit.Rule
import org.junit.Test
import org.junit.contrib.java.lang.system.SystemErrRule
import org.junit.contrib.java.lang.system.SystemOutRule
import org.junit.rules.ExpectedException
import org.junit.runner.RunWith

@RunWith(FlankTestRunner::class)
class AndroidArgsFileTest {

@Rule
@JvmField
val exceptionRule = ExpectedException.none()!!

@Rule
@JvmField
val systemErrRule = SystemErrRule().muteForSuccessfulTests()!!
Expand Down Expand Up @@ -85,17 +90,13 @@ class AndroidArgsFileTest {
}

private fun configWithTestMethods(amount: Int, testShards: Int = 1): AndroidArgs {
val testMethods = mutableListOf<String>()
// test names must be unique otherwise the Set<String> will add them only once.
repeat(amount) { index -> testMethods.add(testName + index) }

return AndroidArgs(
GcloudYml(GcloudYmlParams()),
AndroidGcloudYml(
AndroidGcloudYmlParams(
app = appApkLocal,
test = testApkLocal,
testTargets = testMethods
test = getString("src/test/kotlin/ftl/fixtures/tmp/apk/app-debug-androidTest_$amount.apk")
)
),
FlankYml(
Expand All @@ -107,22 +108,34 @@ class AndroidArgsFileTest {
}

@Test
fun calculateShards() {
var config = configWithTestMethods(1)
fun calculateShards_0() {
exceptionRule.expectMessage("Test APK has no tests")
configWithTestMethods(0)
}

@Test
fun calculateShards_1() {
val config = configWithTestMethods(1)
with(config) {
assert(testShards, 1)
assert(testShardChunks.size, 1)
assert(testShardChunks.first().size, 1)
}
}

config = configWithTestMethods(155)
@Test
fun calculateShards_155() {
val config = configWithTestMethods(155)
with(config) {
assert(testShards, 1)
assert(testShardChunks.size, 1)
assert(testShardChunks.first().size, 155)
}
}

config = configWithTestMethods(155, testShards = 40)
@Test
fun calculateShards_155_40() {
val config = configWithTestMethods(155, testShards = 40)
with(config) {
assert(testShards, 40)
assert(testShardChunks.size, 39)
Expand Down
35 changes: 28 additions & 7 deletions test_runner/src/test/kotlin/ftl/args/ArgsHelperTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ class ArgsHelperTest {
fun calculateShards_fails_emptyShardChunks() {
exceptionRule.expectMessage("Failed to populate test shard chunks")
calculateShards(
testTargets = listOf(""),
validTestNames = listOf(""),
testMethodsToShard = listOf(""),
testMethodsAlwaysRun = listOf(""),
testShards = 1
)
Expand All @@ -108,21 +107,43 @@ class ArgsHelperTest {
@Test
fun calculateShards_succeeds() {
calculateShards(
testTargets = listOf("a", "b"),
validTestNames = listOf("a", "b", "c"),
testMethodsToShard = listOf("a", "b", "c"),
testMethodsAlwaysRun = listOf("c"),
testShards = -1
)
}

@Test
fun calculateShards_emptyTestTargets() {
calculateShards(
testTargets = listOf(),
validTestNames = listOf("a", "b", "c"),
val tests = listOf(
"class com.example.profile.ProfileTest#testOne",
"class com.example.profile.ProfileTest#testTwo"
)
val shards = calculateShards(
testMethodsToShard = tests,
testMethodsAlwaysRun = emptyList(),
testShards = -1
)
val expectedShards = listOf(
listOf(tests[0]),
listOf(tests[1])
)
assertThat(shards).isEqualTo(expectedShards)
}

@Test
fun calculateShards_packageTarget() {
val shards = calculateShards(
testMethodsToShard = listOf("a", "b", "c"),
testMethodsAlwaysRun = listOf("c"),
testShards = 2
)

val expectedShards = listOf(
listOf("c", "a"),
listOf("c", "b")
)
assertThat(shards).isEqualTo(expectedShards)
}

@Test
Expand Down
2 changes: 1 addition & 1 deletion test_runner/src/test/kotlin/ftl/args/FlankYmlTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class FlankYmlTest {

@Rule
@JvmField
val exceptionRule = ExpectedException.none()
val exceptionRule = ExpectedException.none()!!

@Rule
@JvmField
Expand Down

0 comments on commit 7bb7531

Please sign in to comment.