-
Notifications
You must be signed in to change notification settings - Fork 119
/
AndroidArgs.kt
200 lines (180 loc) · 7.93 KB
/
AndroidArgs.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package ftl.args
import com.linkedin.dex.parser.DexParser
import com.linkedin.dex.parser.TestMethod
import ftl.android.AndroidCatalog
import ftl.android.IncompatibleModelVersion
import ftl.android.SupportedDeviceConfig
import ftl.android.UnsupportedModelId
import ftl.android.UnsupportedVersionId
import ftl.args.ArgsHelper.assertFileExists
import ftl.args.ArgsHelper.assertGcsFileExists
import ftl.args.ArgsHelper.calculateShards
import ftl.args.ArgsHelper.createGcsBucket
import ftl.args.ArgsHelper.createJunitBucket
import ftl.args.ArgsHelper.evaluateFilePath
import ftl.args.ArgsHelper.mergeYmlMaps
import ftl.args.ArgsHelper.yamlMapper
import ftl.args.ArgsToString.devicesToString
import ftl.args.ArgsToString.listToString
import ftl.args.ArgsToString.mapToString
import ftl.args.yml.AndroidGcloudYml
import ftl.args.yml.FlankYml
import ftl.args.yml.GcloudYml
import ftl.cli.firebase.test.android.AndroidRunCommand
import ftl.config.Device
import ftl.config.FtlConstants
import ftl.config.FtlConstants.useMock
import ftl.filter.TestFilters
import ftl.gc.GcStorage
import ftl.util.Utils
import java.nio.file.Files
import java.nio.file.Path
import kotlinx.coroutines.runBlocking
// set default values, init properties, etc.
class AndroidArgs(
gcloudYml: GcloudYml,
androidGcloudYml: AndroidGcloudYml,
flankYml: FlankYml,
override val data: String,
val cli: AndroidRunCommand? = null
) : IArgs {
private val gcloud = gcloudYml.gcloud
override val resultsBucket: String
override val resultsDir = cli?.resultsDir ?: gcloud.resultsDir
override val recordVideo = cli?.recordVideo ?: cli?.noRecordVideo?.not() ?: gcloud.recordVideo
override val testTimeout = cli?.timeout ?: gcloud.timeout
override val async = cli?.async ?: gcloud.async
override val project = cli?.project ?: gcloud.project
override val resultsHistoryName = cli?.resultsHistoryName ?: gcloud.resultsHistoryName
override val flakyTestAttempts = cli?.flakyTestAttempts ?: gcloud.flakyTestAttempts
private val androidGcloud = androidGcloudYml.gcloud
var appApk = cli?.app ?: androidGcloud.app
var testApk = cli?.test ?: androidGcloud.test
val autoGoogleLogin = cli?.autoGoogleLogin ?: cli?.noAutoGoogleLogin?.not() ?: androidGcloud.autoGoogleLogin
// We use not() on noUseOrchestrator because if the flag is on, useOrchestrator needs to be false
val useOrchestrator = cli?.useOrchestrator ?: cli?.noUseOrchestrator?.not() ?: androidGcloud.useOrchestrator
val environmentVariables = cli?.environmentVariables ?: androidGcloud.environmentVariables
val directoriesToPull = cli?.directoriesToPull ?: androidGcloud.directoriesToPull
val performanceMetrics = cli?.performanceMetrics ?: cli?.noPerformanceMetrics?.not() ?: androidGcloud.performanceMetrics
val testTargets = cli?.testTargets ?: androidGcloud.testTargets
val devices = cli?.device ?: androidGcloud.device
private val flank = flankYml.flank
override val testShards = cli?.testShards ?: flank.testShards
override val shardTime = cli?.shardTime ?: flank.shardTime
override val repeatTests = cli?.repeatTests ?: flank.repeatTests
override val smartFlankGcsPath = flank.smartFlankGcsPath
override val testTargetsAlwaysRun = cli?.testTargetsAlwaysRun ?: flank.testTargetsAlwaysRun
override val filesToDownload = cli?.filesToDownload ?: flank.filesToDownload
override val disableSharding = cli?.disableSharding ?: flank.disableSharding
// computed properties not specified in yaml
override val testShardChunks: List<List<String>> by lazy {
if (disableSharding) return@lazy listOf(emptyList<String>())
// Download test APK if necessary so it can be used to validate test methods
var testLocalApk = testApk
if (testApk.startsWith(FtlConstants.GCS_PREFIX)) {
runBlocking {
testLocalApk = GcStorage.downloadTestApk(this@AndroidArgs)
}
}
val filteredTests = getTestMethods(testLocalApk)
calculateShards(filteredTests, this)
}
init {
resultsBucket = createGcsBucket(project, cli?.resultsBucket ?: gcloud.resultsBucket)
createJunitBucket(project, flank.smartFlankGcsPath)
if (appApk.startsWith(FtlConstants.GCS_PREFIX)) {
assertGcsFileExists(appApk)
} else {
appApk = evaluateFilePath(appApk)
assertFileExists(appApk, "appApk")
}
if (testApk.startsWith(FtlConstants.GCS_PREFIX)) {
assertGcsFileExists(testApk)
} else {
testApk = evaluateFilePath(testApk)
assertFileExists(testApk, "testApk")
}
devices.forEach { device -> assertDeviceSupported(device) }
}
private fun getTestMethods(testLocalApk: String): List<String> {
val allTestMethods = DexParser.findTestMethods(testLocalApk)
require(allTestMethods.isNotEmpty()) { Utils.fatalError("Test APK has no tests") }
val testFilter = TestFilters.fromTestTargets(testTargets)
val filteredTests = allTestMethods
.asSequence()
.distinct()
.filter(testFilter.shouldRun)
.map(TestMethod::testName)
.map { "class $it" }
.toList()
require(useMock || filteredTests.isNotEmpty()) { Utils.fatalError("All tests filtered out") }
return filteredTests
}
private fun assertDeviceSupported(device: Device) {
val deviceConfigTest = AndroidCatalog.supportedDeviceConfig(device.model, device.version)
when (deviceConfigTest) {
SupportedDeviceConfig -> {
}
UnsupportedModelId -> throw RuntimeException("Unsupported model id, '${device.model}'\nSupported model ids: ${AndroidCatalog.androidModelIds}")
UnsupportedVersionId -> throw RuntimeException("Unsupported version id, '${device.version}'\nSupported Version ids: ${AndroidCatalog.androidVersionIds}")
is IncompatibleModelVersion -> throw RuntimeException("Incompatible model, '${device.model}', and version, '${device.version}'\nSupported version ids for '${device.model}': $deviceConfigTest")
}
}
override fun toString(): String {
return """
AndroidArgs
gcloud:
results-bucket: $resultsBucket
results-dir: $resultsDir
record-video: $recordVideo
timeout: $testTimeout
async: $async
project: $project
results-history-name: $resultsHistoryName
# Android gcloud
app: $appApk
test: $testApk
auto-google-login: $autoGoogleLogin
use-orchestrator: $useOrchestrator
environment-variables:
${mapToString(environmentVariables)}
directories-to-pull:
${listToString(directoriesToPull)}
performance-metrics: $performanceMetrics
test-targets:
${listToString(testTargets)}
device:
${devicesToString(devices)}
flaky-test-attempts: $flakyTestAttempts
flank:
testShards: $testShards
shardTime: $shardTime
repeatTests: $repeatTests
smartFlankGcsPath: $smartFlankGcsPath
files-to-download:
${listToString(filesToDownload)}
test-targets-always-run:
${listToString(testTargetsAlwaysRun)}
disableSharding: $disableSharding
""".trimIndent()
}
companion object : IArgsCompanion {
override val validArgs by lazy {
mergeYmlMaps(GcloudYml, AndroidGcloudYml, FlankYml)
}
fun load(data: Path, cli: AndroidRunCommand? = null): AndroidArgs =
load(String(Files.readAllBytes(data)), cli)
fun load(data: String, cli: AndroidRunCommand? = null): AndroidArgs {
val flankYml = yamlMapper.readValue(data, FlankYml::class.java)
val gcloudYml = yamlMapper.readValue(data, GcloudYml::class.java)
val androidGcloudYml = yamlMapper.readValue(data, AndroidGcloudYml::class.java)
return AndroidArgs(
gcloudYml,
androidGcloudYml,
flankYml,
data,
cli
)
}
}
}