Skip to content

Commit

Permalink
Merge branch 'master' into 2068_analytics_to_flank_wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
Sloox authored Jul 14, 2021
2 parents 13408a2 + 1ca2f77 commit dd4020c
Show file tree
Hide file tree
Showing 30 changed files with 209 additions and 95 deletions.
1 change: 1 addition & 0 deletions corellium/domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" }
dependencies {
implementation(Dependencies.KOTLIN_COROUTINES_CORE)
api(project(":corellium:api"))
api(project(":tool:execution:linear"))
api(project(":tool:apk"))
api(project(":tool:filter"))
api(project(":tool:shard:calculate"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import flank.corellium.domain.run.test.android.step.loadPreviousDurations
import flank.corellium.domain.run.test.android.step.parseApksInfo
import flank.corellium.domain.run.test.android.step.parseTestCasesFromApks
import flank.corellium.domain.run.test.android.step.prepareShards
import flank.corellium.domain.util.Transform
import flank.corellium.domain.util.execute
import flank.corellium.domain.util.injectLogger
import flank.exection.linear.Transform
import flank.exection.linear.execute
import flank.exection.linear.injectLogger
import flank.instrument.log.Instrument
import flank.junit.JUnit
import flank.log.Event
Expand Down
10 changes: 7 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -588,12 +588,16 @@ gcloud:
# - class com.package2.for.shard2.Class

### parameterized-tests
## Specifies how to handle tests which contain Parameterization.
## 3 options are available
## Specifies how to handle tests which contain the parameterization annotation.
## 4 options are available
## default: treat Parameterized tests as normal and shard accordingly
## ignore-all: Parameterized tests are ignored and not sharded
## shard-into-single: Parameterized tests are collected and put into a single shard
## Note: if left blank default is used. Default usage may result in significant increase/difference of shard times observed
## shard-into-multiple: Parameterized tests are collected and sharded into different shards based upon matching names. (Experimental)
## Note: If left blank default is used. Default usage may result in significant increase/difference of shard times observed
## Note: If shard-into-single is used, a single additional shard is created that will run the Parameterized tests separately.
## Note: If shard-into-multiple is used, each parameterized test will be matched by its corresponding name and sharded into a separate shard.
## This may dramatically increase the amount of expected shards depending upon how many parameterized tests are discovered.
# parameterized-tests: default

flank:
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ include(
":tool:log",
":tool:log:format",
":tool:execution:parallel",
":tool:execution:synchronized",
":tool:execution:linear",
)

plugins {
Expand Down
6 changes: 5 additions & 1 deletion test_runner/flank.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ gcloud:
## default: treat Parameterized tests as normal and shard accordingly
## ignore-all: Parameterized tests are ignored and not sharded
## shard-into-single: Parameterized tests are collected and put into a single shard
## Note: if left blank default is used. Default usage may result in significant increase/difference of shard times observed
## shard-into-multiple: Parameterized tests are collected and sharded into different shards based upon matching names. (Experimental)
## Note: If left blank default is used. Default usage may result in significant increase/difference of shard times observed
## Note: If shard-into-single is used, a single additional shard is created that will run the Parameterized tests separately.
## Note: If shard-into-multiple is used, each parameterized test will be matched by its corresponding name and sharded into a separate shard.
## This may dramatically increase the amount of expected shards depending upon how many parameterized tests are discovered.
# parameterized-tests: default

flank:
Expand Down
11 changes: 10 additions & 1 deletion test_runner/src/main/kotlin/ftl/args/ValidateAndroidArgs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,16 @@ private fun AndroidArgs.assertParameterizedTests() {
if (parameterizedTests.isNullOrEmpty() || parameterizedTests !in listOf(
"ignore-all",
"default",
"shard-into-single"
"shard-into-single",
"shard-into-multiple"
)
) throw FlankConfigurationError("Parameterized test flag must be one of the following: `default`, `ignore-all`, `shard-into-single`, leaving it blank will result in `default` sharding.")
else checkParameterizedTestFeaturesWarning()
}

private fun AndroidArgs.checkParameterizedTestFeaturesWarning() {
when (parameterizedTests) {
"shard-into-single" -> logLn("WARNING: All parameterized tests will be collected and sharded separately into a single shard. This may result in a long shard execution times if many parameterized tests are found.")
"shard-into-multiple" -> logLn("WARNING: All parameterized tests will be collected and sharded into different shards. This will result in additional shards created. max-shards is not adhered to for this selection.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,13 @@ data class AndroidGcloudConfig @JsonIgnore constructor(

@set:CommandLine.Option(
names = ["--parameterized-tests"],
description = ["Specifies how to handle tests which contain the parameterization annotation. Possible values: `default`, `ignore-all`, `shard-into-single`, leaving it blank will result in `default` sharding"]
description = [
"Specifies how to handle tests which contain the parameterization annotation. Possible values: `default`, `ignore-all`, `shard-into-single`, `shard-into-multiple`.\n" +
"leaving it blank will result in `default` sharding.\n" +
"Note: Making use of shard-into-single` or `shard-into-multiple will result in additional shards being created even if a max number of shards has been specified.\n" +
"Note: If shard-into-single is used, a single additional shard is created that will run the Parameterized tests separately.\n" +
"Note: If shard-into-multiple is used, each parameterized test will be matched by its corresponding name and sharded into a separate shard. This may dramatically increase the amount of expected shards depending upon how many parameterized tests are discovered."
]
)
@set:JsonProperty("parameterized-tests")
var parameterizedTests: String? by data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,15 @@ private fun InstrumentationTestContext.calculateShardsNormally(
private fun InstrumentationTestContext.calculateShards(
args: AndroidArgs,
testFilter: TestFilter = TestFilters.fromTestTargets(args.testTargets, args.testTargetsForShard)
): InstrumentationTestContext =
if (args.parameterizedTests.shouldShardIntoSingle()) {
var flankTestMethods = getFlankTestMethods(testFilter, args.parameterizedTests)
val parameterizedTests = flankTestMethods.filter { it.isParameterizedClass }
flankTestMethods = flankTestMethods.filterNot { it.isParameterizedClass }
val shards = calculateParameterizedShards(flankTestMethods, args)
val parameterizedShard = calculateParameterizedShards(parameterizedTests, args, 1)
shards.copy(shards = shards.shards + parameterizedShard.shards)
} else calculateShardsNormally(args, testFilter)
): InstrumentationTestContext = if (args.parameterizedTests.shouldShardSmartly()) {
var flankTestMethods = getFlankTestMethods(testFilter, args.parameterizedTests)
val parameterizedTests = flankTestMethods.filter { it.isParameterizedClass }
flankTestMethods = flankTestMethods.filterNot { it.isParameterizedClass }
val shards = calculateParameterizedShards(flankTestMethods, args)
val shardCount = if (args.parameterizedTests.isSingleParameterizedShard()) 1 else parameterizedTests.size
val parameterizedShard = calculateParameterizedShards(parameterizedTests, args, shardCount)
shards.copy(shards = shards.shards + parameterizedShard.shards)
} else calculateShardsNormally(args, testFilter)

private fun InstrumentationTestContext.calculateParameterizedShards(
filteredTests: List<FlankTestMethod>,
Expand All @@ -121,8 +121,6 @@ private fun InstrumentationTestContext.calculateParameterizedShards(
)
}

private fun String.shouldShardIntoSingle() = (this == "shard-into-single")

private fun InstrumentationTestContext.calculateDummyShards(
args: AndroidArgs,
testFilter: TestFilter = TestFilters.fromTestTargets(args.testTargets, args.testTargetsForShard)
Expand Down Expand Up @@ -177,6 +175,8 @@ private val ignoredAnnotations = listOf(
"android.support.test.filters.Suppress"
)

private fun String.shouldShardSmartly() = (this == "shard-into-single" || this == "shard-into-multiple")
private fun String.isSingleParameterizedShard() = (this == "shard-into-single")
private fun String.shouldIgnore(): Boolean = (this == "ignore-all")

@VisibleForTesting
Expand Down
14 changes: 14 additions & 0 deletions test_runner/src/test/kotlin/ftl/args/AndroidArgsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2832,6 +2832,20 @@ AndroidArgs
val chunks = runBlocking { parsedYml.runAndroidTests() }.shardChunks
assertTrue(chunks.size == 1)
}

@Test
fun `should shard tests into multiple new shards with shard-into-multiple`() {
val yaml = """
gcloud:
app: $appApk
test: $testExtremeParameterizedOtherApk
parameterized-tests: shard-into-multiple
""".trimIndent()

val parsedYml = AndroidArgs.load(yaml).validate()
val chunks = runBlocking { parsedYml.runAndroidTests() }.shardChunks
assertTrue(chunks.size == 5)
}
}

private fun AndroidArgs.Companion.load(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ repositories {
tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" }

dependencies {
api(project(":tool:log"))
implementation(Dependencies.KOTLIN_COROUTINES_CORE)
testImplementation(Dependencies.JUNIT)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package flank.corellium.domain.util
package flank.exection.linear

import flank.log.Event
import flank.log.Logger
Expand All @@ -14,13 +14,13 @@ import kotlinx.coroutines.flow.fold
* @receiver Value to apply transformations on it.
* @return Result of transformations.
*/
internal suspend infix fun <S> S.execute(operations: Flow<Transform<S>>): S =
suspend infix fun <S> S.execute(operations: Flow<Transform<S>>): S =
operations.fold(this) { value, transform -> transform(value) }

/**
* Simple parameterized factory for generating [Transform] instances.
*/
internal class CreateTransformation<S> : (Transform<S>) -> Transform<S> by { it }
class CreateTransformation<S> : (Transform<S>) -> Transform<S> by { it }

/**
* Type-alias for suspendable transforming operation
Expand All @@ -30,7 +30,7 @@ typealias Transform<S> = suspend S.() -> S
/**
* Inject log output to the transform function.
*/
internal fun <S> Logger.injectLogger(
fun <S> Logger.injectLogger(
type: Any = Unit,
transform: suspend S.(Output) -> S
): Transform<S> {
Expand Down
1 change: 1 addition & 0 deletions tool/execution/parallel/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ repositories {
tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" }

dependencies {
api(project(":tool:log"))
implementation(Dependencies.KOTLIN_COROUTINES_CORE)
testImplementation(Dependencies.JUNIT)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package flank.exection.parallel

import flank.exection.parallel.internal.DynamicType
import flank.exection.parallel.internal.EagerProperties

// ======================= Signature =======================
Expand Down Expand Up @@ -34,9 +35,21 @@ infix fun <R : Any> Parallel.Type<R>.using(

/**
* Factory function for creating special task that can validate arguments before execution.
* Typically the [Parallel.Context] with added [EagerProperties] is used to validate initial state.
* The [Parallel.Context] with added [EagerProperties] can validate if state contains required initial values.
*/
internal fun <C : Parallel.Context> validator(
context: (() -> C)
): Parallel.Task<Unit> =
context() using { context().also { it.state = this }.run { validate() } }

// ======================= Type =======================

/**
* Factory function for creating dynamic [Parallel.Type].
*/
inline fun <reified T : Any> type(): Parallel.Type<T> = type(T::class.java)

/**
* Factory function for creating dynamic [Parallel.Type].
*/
fun <T : Any> type(type: Class<T>): Parallel.Type<T> = DynamicType(type)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package flank.exection.parallel

import flank.exection.parallel.internal.Execution
import flank.exection.parallel.internal.invoke
import flank.exection.parallel.internal.minusContextValidators
import kotlinx.coroutines.flow.Flow

/**
Expand All @@ -12,7 +13,7 @@ import kotlinx.coroutines.flow.Flow
infix operator fun Tasks.invoke(
args: ParallelState
): Flow<ParallelState> =
Execution(this, args).invoke()
Execution(minusContextValidators(), args).invoke()

// ======================= Extensions =======================

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package flank.exection.parallel
import flank.exection.parallel.internal.ContextProvider
import flank.exection.parallel.internal.EagerProperties
import flank.exection.parallel.internal.lazyProperty
import java.lang.System.currentTimeMillis
import flank.log.Output

// ======================= Types =======================

Expand All @@ -13,7 +13,7 @@ import java.lang.System.currentTimeMillis
object Parallel {

/**
* Abstraction for execution data provider which is also an context for task execution.
* Abstraction for execution data provider which is also a context for task execution.
* For initialization purpose some properties are exposed as variable.
*/
open class Context : Type<Unit> {
Expand Down Expand Up @@ -46,7 +46,12 @@ object Parallel {
protected operator fun <T : Any> Type<T>.unaryMinus() = lazyProperty(this)

/**
* Internal accessor for initializing (validating) eager properties
* DSL for creating lazy delegate accessor to the state value for a given type with additional property selector.
*/
protected operator fun <T : Any, V> Type<T>.invoke(select: T.() -> V) = lazyProperty(this, select)

/**
* Internal accessor for initializing (validating) eager properties.
*/
internal fun validate() = eager()
}
Expand All @@ -67,8 +72,8 @@ object Parallel {
/**
* The task signature.
*
* @param type A return type of a task
* @param args A set of types for arguments
* @param type A return a type of task.
* @param args A set of argument types.
*/
data class Signature<R : Any>(
val type: Type<R>,
Expand All @@ -81,15 +86,6 @@ object Parallel {
*/
class Function<X : Context>(override val context: () -> X) : ContextProvider<X>()

data class Event internal constructor(
val type: Type<*>,
val data: Any,
val timestamp: Long = currentTimeMillis(),
) {
object Start
object Stop
}

object Logger : Type<Output>

/**
Expand All @@ -111,11 +107,6 @@ object Parallel {
*/
typealias ExecuteTask<R> = suspend ParallelState.() -> R

/**
* Common signature for structural log output.
*/
typealias Output = Any.() -> Unit

/**
* Immutable state for parallel execution.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
package flank.exection.parallel

import flank.exection.parallel.internal.contextValidators
import flank.exection.parallel.internal.contextValidatorTypes
import flank.exection.parallel.internal.reduceTo
import flank.exection.parallel.internal.type

/**
* Reduce given [Tasks] by [expected] types to remove unneeded tasks from the graph.
* The returned graph will hold only tasks that are returning selected types, their dependencies and derived dependencies.
* Additionally this is keeping also the validators for initial state.
* Additionally, this is keeping also the validators for initial state.
*
* @return Reduced [Tasks]
*/
operator fun Tasks.invoke(
expected: Set<Parallel.Type<*>>
): Tasks =
reduceTo(expected + contextValidators())
reduceTo(expected + contextValidatorTypes())

/**
* Shortcut for tasks reducing.
*/
operator fun Tasks.invoke(
vararg expected: Parallel.Type<*>
): Tasks = invoke(expected.toSet())

/**
* Remove the [Tasks] by given [types].
*/
operator fun Tasks.minus(
types: Set<Parallel.Type<*>>
): Tasks =
filterNot { task -> task.type in types }.toSet()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package flank.exection.parallel

/**
* Select value by type.
*/
@Suppress("UNCHECKED_CAST")
fun <T : Any> ParallelState.select(type: Parallel.Type<T>) = get(type) as T
Loading

0 comments on commit dd4020c

Please sign in to comment.