Skip to content

Commit

Permalink
refactor: Data scratch - test matrix (#1901)
Browse files Browse the repository at this point in the history
Fixes #1756 

## Test Plan
> How do we know the code works?

* All tests pass
* Flank works as before

## Checklist

- [X] Unit tests updated
  • Loading branch information
adamfilipow92 authored May 11, 2021
1 parent 1c5f14c commit 66e796c
Show file tree
Hide file tree
Showing 56 changed files with 816 additions and 586 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ class CustomShardingIT {
.map { it.testAxises }
.flatten()

assertThat(testsResults.sumBy { it.testSuiteOverview.failures }).isEqualTo(5)
assertThat(testsResults.sumBy { it.testSuiteOverview.total }).isEqualTo(41)
assertThat(testsResults.sumBy { it.suiteOverview.failures }).isEqualTo(5)
assertThat(testsResults.sumBy { it.suiteOverview.total }).isEqualTo(41)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class MultipleApksIT {
.map { it.testAxises }
.flatten()

assertThat(testsResults.sumBy { it.testSuiteOverview.failures }).isEqualTo(5)
assertThat(testsResults.sumBy { it.testSuiteOverview.total }).isEqualTo(41)
assertThat(testsResults.sumBy { it.suiteOverview.failures }).isEqualTo(5)
assertThat(testsResults.sumBy { it.suiteOverview.total }).isEqualTo(41)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class MultipleDevicesIT {
.map { it.testAxises }
.flatten()

assertThat(testsResults.sumBy { it.testSuiteOverview.failures }).isEqualTo(15)
assertThat(testsResults.sumBy { it.testSuiteOverview.total }).isEqualTo(123)
assertThat(testsResults.sumBy { it.suiteOverview.failures }).isEqualTo(15)
assertThat(testsResults.sumBy { it.suiteOverview.total }).isEqualTo(123)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ fun assertContainsUploads(input: String, vararg uploads: String) = uploads.forEa
assertThat(input).contains("Uploading [$it]")
}

fun TestSuiteOverview.assertTestCountMatches(
fun SuiteOverview.assertTestCountMatches(
total: Int = 0,
errors: Int = 0,
failures: Int = 0,
Expand Down
32 changes: 16 additions & 16 deletions integration_tests/src/test/kotlin/utils/OutputReportParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ private val jsonMapper by lazy { JsonMapper().registerModule(KotlinModule()) }

fun String.asOutputReport() = jsonMapper.readValue<OutputReport>(this)

val OutputReport.firstTestSuiteOverview: TestSuiteOverview
get() = testResults.values.first().testAxises.first().testSuiteOverview
val OutputReport.firstTestSuiteOverview: SuiteOverview
get() = testResults.values.first().testAxises.first().suiteOverview

data class OutputReport(
val args: Any,
Expand All @@ -23,22 +23,22 @@ data class OutputReport(

data class Matrix(
val app: String = "",
@JsonProperty("test-axises") val testAxises: List<TextAxis>
@JsonProperty("test-axises") val testAxises: List<Outcome> = emptyList()
)

data class TextAxis(
val device: String,
val outcome: String,
val details: String,
val testSuiteOverview: TestSuiteOverview
data class Outcome(
val device: String = "",
val outcome: String = "",
val details: String = "",
val suiteOverview: SuiteOverview = SuiteOverview()
)

data class TestSuiteOverview(
val total: Int,
val errors: Int,
val failures: Int,
val flakes: Int,
val skipped: Int,
val elapsedTime: Double,
val overheadTime: Double
data class SuiteOverview(
val total: Int = 0,
val errors: Int = 0,
val failures: Int = 0,
val flakes: Int = 0,
val skipped: Int = 0,
val elapsedTime: Double = 0.0,
val overheadTime: Double = 0.0
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package ftl.adapter

import ftl.adapter.google.toApiModel
import ftl.api.JUnitTest
import ftl.client.google.createAndUploadPerformanceMetricsForAndroid
import ftl.client.google.fetchMatrices
import ftl.client.junit.createJUnitTestResult

object GoogleJUnitTestFetch :
JUnitTest.Result.GenerateFromApi,
(JUnitTest.Result.ApiIdentity) -> JUnitTest.Result by { (projectId, matrixIds) ->
fetchMatrices(matrixIds, projectId)
(JUnitTest.Result.ApiIdentity) -> JUnitTest.Result by { (args, matrixMap) ->
fetchMatrices(matrixMap.map.values.map { it.matrixId }, args.project)
.createAndUploadPerformanceMetricsForAndroid(args, matrixMap)
.createJUnitTestResult()
.toApiModel()
}
13 changes: 13 additions & 0 deletions test_runner/src/main/kotlin/ftl/adapter/TestMatrixCancel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ftl.adapter

import ftl.api.TestMatrix
import ftl.client.google.cancelMatrices
import kotlinx.coroutines.runBlocking

object TestMatrixCancel :
TestMatrix.Cancel,
(TestMatrix.Identity) -> Unit by { identity ->
runBlocking {
cancelMatrices(identity.matrixId, identity.projectId)
}
}
11 changes: 11 additions & 0 deletions test_runner/src/main/kotlin/ftl/adapter/TestMatrixFetch.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ftl.adapter

import ftl.adapter.google.toApiModel
import ftl.api.TestMatrix
import ftl.client.google.fetchMatrixOutcome

object TestMatrixFetch :
TestMatrix.Summary.Fetch,
(TestMatrix.Data) -> TestMatrix.Summary by {
fetchMatrixOutcome(it).toApiModel()
}
14 changes: 14 additions & 0 deletions test_runner/src/main/kotlin/ftl/adapter/TestMatrixRefresh.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ftl.adapter

import ftl.adapter.google.toApiModel
import ftl.api.TestMatrix
import ftl.client.google.refreshMatrix
import kotlinx.coroutines.runBlocking

object TestMatrixRefresh :
TestMatrix.Refresh,
(TestMatrix.Identity) -> TestMatrix.Data by { identity ->
runBlocking {
refreshMatrix(identity.matrixId, identity.projectId).toApiModel(identity)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package ftl.adapter.google

import com.google.testing.model.FileReference
import com.google.testing.model.TestExecution
import com.google.testing.model.TestMatrix
import ftl.analytics.toJSONObject
import ftl.api.TestMatrix.Data
import ftl.api.TestMatrix.Outcome
import ftl.api.TestMatrix.SuiteOverview
import ftl.client.google.TestOutcome
import ftl.environment.orUnknown
import ftl.run.common.prettyPrint
import ftl.util.MatrixState
import ftl.util.getClientDetails
import ftl.util.getGcsPath
import ftl.util.getGcsPathWithoutRootBucket
import ftl.util.getGcsRootBucket
import ftl.util.timeoutToSeconds
import ftl.util.webLink
import ftl.util.webLinkWithoutExecutionDetails

fun TestMatrix.toApiModel(identity: ftl.api.TestMatrix.Identity? = null) = Data(
projectId = projectId.orEmpty(),
matrixId = testMatrixId.orEmpty(),
gcsPath = getGcsPath(),
webLink = webLink(),
downloaded = false,
clientDetails = getClientDetails(),
gcsPathWithoutRootBucket = getGcsPathWithoutRootBucket(),
gcsRootBucket = getGcsRootBucket(),
webLinkWithoutExecutionDetails = webLinkWithoutExecutionDetails(),
appFileName = extractAppFileName() ?: fallbackAppName,
isCompleted = MatrixState.completed(state) &&
testExecutions?.all { MatrixState.completed(it.state.orEmpty()) } ?: true,
testExecutions = testExecutions?.toApiModel().orEmpty(),
testTimeout = testTimeout(),
isRoboTest = isRoboTest(),
historyId = resultStorage?.toolResultsExecution?.historyId ?: identity?.historyId.orEmpty(),
executionId = resultStorage?.toolResultsExecution?.executionId ?: identity?.executionId.orEmpty(),
invalidMatrixDetails = invalidMatrixDetails.orUnknown(),
state = state.orEmpty(),
)

private fun TestMatrix.testTimeout() = timeoutToSeconds(
testExecutions
?.firstOrNull { it?.testSpecification?.testTimeout != null }
?.testSpecification
?.testTimeout
?: "0s"
)

private fun TestMatrix.isRoboTest() = testExecutions.orEmpty().any { it?.testSpecification?.androidRoboTest != null }

private const val fallbackAppName = "N/A"

fun TestOutcome.toApiModel() = Outcome(
device, outcome, details,
SuiteOverview(
testSuiteOverview.total,
testSuiteOverview.errors,
testSuiteOverview.failures,
testSuiteOverview.flakes,
testSuiteOverview.skipped,
testSuiteOverview.elapsedTime,
testSuiteOverview.overheadTime
)
)

fun List<TestExecution>.toApiModel() = map(TestExecution::toApiModel)

fun TestExecution.toApiModel() = ftl.api.TestMatrix.TestExecution(
id = id.orEmpty(),
modelId = environment?.androidDevice?.androidModelId
?: environment?.iosDevice?.iosModelId ?: "",
deviceVersion = environment?.androidDevice?.androidVersionId
?: environment?.iosDevice?.iosVersionId ?: "",
shardIndex = shard?.shardIndex,
state = state.orUnknown(),
errorMessage = testDetails?.errorMessage.orEmpty(),
progress = testDetails?.progressMessages ?: emptyList(),
toolResultsStep = toolResultsStep
)

private fun TestMatrix.extractAppFileName() = testSpecification?.run {
listOf(
androidInstrumentationTest,
androidTestLoop,
androidRoboTest,
iosXcTest,
iosTestLoop
)
.firstOrNull { it != null }
?.toJSONObject()
?.let { prettyPrint.fromJson(it.toString(), AppPath::class.java).gcsPath }
?.substringAfterLast('/')
}

private data class AppPath(
private val appApk: FileReference?,
private val testsZip: FileReference?,
private val appIpa: FileReference?
) {
val gcsPath: String?
get() = (appApk ?: testsZip ?: appIpa)?.gcsPath
}
15 changes: 15 additions & 0 deletions test_runner/src/main/kotlin/ftl/adapter/google/SummaryAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ftl.adapter.google

import ftl.api.TestMatrix
import ftl.client.google.BillableMinutes
import ftl.client.google.TestOutcome

fun Pair<BillableMinutes, List<TestOutcome>>.toApiModel(): TestMatrix.Summary {
val (billableMinutes, outcomes) = this
return TestMatrix.Summary(
billableMinutes = billableMinutes.toApiModel(),
axes = outcomes.map(TestOutcome::toApiModel)
)
}

private fun BillableMinutes.toApiModel() = TestMatrix.BillableMinutes(virtual, physical)
6 changes: 4 additions & 2 deletions test_runner/src/main/kotlin/ftl/api/JUnitTestResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement
import ftl.adapter.GoogleJUnitTestFetch
import ftl.adapter.GoogleJUnitTestParse
import ftl.adapter.GoogleLegacyJunitTestParse
import ftl.args.IArgs
import ftl.json.MatrixMap
import java.io.File

val generateJUnitTestResultFromApi: JUnitTest.Result.GenerateFromApi get() = GoogleJUnitTestFetch
Expand All @@ -21,8 +23,8 @@ object JUnitTest {
var testsuites: MutableList<Suite>? = null
) {
data class ApiIdentity(
val projectId: String,
val matrixIds: List<String>
val args: IArgs,
val matrixMap: MatrixMap
)

interface GenerateFromApi : (ApiIdentity) -> Result
Expand Down
96 changes: 96 additions & 0 deletions test_runner/src/main/kotlin/ftl/api/TestMatrix.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package ftl.api

import com.google.testing.model.ToolResultsStep
import ftl.adapter.TestMatrixCancel
import ftl.adapter.TestMatrixFetch
import ftl.adapter.TestMatrixRefresh
import ftl.util.StepOutcome

val refreshTestMatrix: TestMatrix.Refresh get() = TestMatrixRefresh
val cancelTestMatrix: TestMatrix.Cancel get() = TestMatrixCancel
val fetchTestSummary: TestMatrix.Summary.Fetch get() = TestMatrixFetch

private const val fallbackAppName = "N/A"

object TestMatrix {

data class Result(
val runPath: String,
val map: Map<String, Data>,
)

data class Data(
val projectId: String = "",
val matrixId: String = "",
val state: String = "",
val gcsPath: String = "",
val webLink: String = "",
val downloaded: Boolean = false,
val billableMinutes: BillableMinutes = BillableMinutes(),
val clientDetails: Map<String, String>? = null,
val gcsPathWithoutRootBucket: String = "",
val gcsRootBucket: String = "",
val webLinkWithoutExecutionDetails: String? = "",
val axes: List<Outcome> = emptyList(),
val appFileName: String = fallbackAppName,
val isCompleted: Boolean = false,
val testExecutions: List<TestExecution> = emptyList(),
val testTimeout: Long = 0,
val isRoboTest: Boolean = false,
val historyId: String = "",
val executionId: String = "",
val invalidMatrixDetails: String = ""
) {
val outcome = axes.maxByOrNull { StepOutcome.order.indexOf(it.outcome) }?.outcome.orEmpty()
}

data class TestExecution(
val id: String,
val modelId: String,
val deviceVersion: String,
val shardIndex: Int?,
val state: String,
val errorMessage: String = "",
val progress: List<String> = emptyList(),
val toolResultsStep: ToolResultsStep?
)

data class Outcome(
val device: String = "",
val outcome: String = "",
val details: String = "",
val suiteOverview: SuiteOverview = SuiteOverview()
)

data class SuiteOverview(
val total: Int = 0,
val errors: Int = 0,
val failures: Int = 0,
val flakes: Int = 0,
val skipped: Int = 0,
val elapsedTime: Double = 0.0,
val overheadTime: Double = 0.0
)

data class BillableMinutes(
val virtual: Long = 0,
val physical: Long = 0
)

data class Summary(
val billableMinutes: BillableMinutes,
val axes: List<Outcome>,
) {
interface Fetch : (Data) -> Summary
}

data class Identity(
val matrixId: String,
val projectId: String,
val historyId: String = "",
val executionId: String = "",
)

interface Cancel : (Identity) -> Unit
interface Refresh : (Identity) -> Data
}
Loading

0 comments on commit 66e796c

Please sign in to comment.