@@ -50,6 +50,97 @@ val e2eBackendEnv = System.getenv("E2E_BACKEND") ?: "local"
5050val e2eHomegateUrlEnv = System .getenv(" E2E_HOMEGATE_URL" ) ? : " http://127.0.0.1:6288"
5151val trezorBridgeEnv = System .getenv(" TREZOR_BRIDGE" )?.toBoolean()?.toString() ? : " false"
5252val trezorBridgeUrlEnv = System .getenv(" TREZOR_BRIDGE_URL" ) ? : " http://10.0.2.2:21325"
53+ val androidTestAnnotationPackage = " to.bitkit.test.annotations"
54+ val androidTestTaskPrefix = " connectedDevDebug"
55+ val androidTestTaskSuffix = " AndroidTest"
56+ val baseAndroidTestTaskName = " $androidTestTaskPrefix$androidTestTaskSuffix "
57+ val androidTestAnnotationNames = file(" src/androidTest/java/to/bitkit/test/annotations" )
58+ .listFiles()
59+ ?.mapNotNull { file ->
60+ file.nameWithoutExtension.takeIf {
61+ file.isFile &&
62+ file.extension == " kt"
63+ }
64+ }
65+ ?.sorted()
66+ .orEmpty()
67+ val requestedTaskNames = gradle.startParameter.taskNames.map { it.substringAfterLast(" :" ) }
68+
69+ fun androidTestTaskName (annotationName : String ): String {
70+ return " $androidTestTaskPrefix$annotationName$androidTestTaskSuffix "
71+ }
72+
73+ fun isTaskNameAbbreviation (taskName : String , fullTaskName : String ): Boolean {
74+ if (taskName.isEmpty() || taskName == fullTaskName) return false
75+ return fullTaskName.startsWith(taskName) ||
76+ taskName.any { it.isUpperCase() } &&
77+ taskName.isSubsequenceOf(fullTaskName)
78+ }
79+
80+ fun String.isSubsequenceOf (value : String ): Boolean {
81+ var searchIndex = 0
82+ for (char in this ) {
83+ searchIndex = value.indexOf(char, startIndex = searchIndex)
84+ if (searchIndex == - 1 ) return false
85+ searchIndex++
86+ }
87+ return true
88+ }
89+
90+ val androidTestTaskNames = androidTestAnnotationNames.map { androidTestTaskName(it) }
91+ val requestedBaseAndroidTestTaskNames = requestedTaskNames.filter { taskName ->
92+ taskName == baseAndroidTestTaskName ||
93+ isTaskNameAbbreviation(taskName, baseAndroidTestTaskName)
94+ }
95+ val abbreviatedAndroidTestTaskNames = requestedTaskNames.filter { taskName ->
96+ taskName !in requestedBaseAndroidTestTaskNames &&
97+ taskName !in androidTestTaskNames &&
98+ androidTestTaskNames.any { isTaskNameAbbreviation(taskName, it) }
99+ }
100+ require(abbreviatedAndroidTestTaskNames.isEmpty()) {
101+ " Use full generated Android test lane task names. Abbreviated lane tasks are unsupported: " +
102+ abbreviatedAndroidTestTaskNames.joinToString(" , " )
103+ }
104+ val requestedAndroidTestAnnotationTaskNames = requestedTaskNames.filter { taskName ->
105+ taskName in androidTestTaskNames
106+ }
107+ require(requestedBaseAndroidTestTaskNames.isEmpty() || requestedAndroidTestAnnotationTaskNames.isEmpty()) {
108+ " Do not combine '$baseAndroidTestTaskName ' with generated Android test lane tasks. Requested lanes: " +
109+ requestedAndroidTestAnnotationTaskNames.joinToString(" , " )
110+ }
111+ require(requestedAndroidTestAnnotationTaskNames.size <= 1 ) {
112+ " Run only one generated Android test lane per Gradle invocation. Requested lanes: " +
113+ requestedAndroidTestAnnotationTaskNames.joinToString(" , " )
114+ }
115+ val requestedAndroidTestAnnotationTask = requestedAndroidTestAnnotationTaskNames.singleOrNull()
116+ val requestedAndroidTestAnnotationTaskName = requestedAndroidTestAnnotationTask?.let { taskName ->
117+ androidTestAnnotationNames.first { androidTestTaskName(it) == taskName }
118+ }
119+ val requestedAndroidTestAnnotation = providers.gradleProperty(" bitkitAndroidTestAnnotation" )
120+ .orNull
121+ ?.trim()
122+ ?.takeIf { it.isNotEmpty() }
123+ ?.also {
124+ require(' .' !in it) {
125+ " Use a simple Android test annotation name, e.g. 'ComposeUi'."
126+ }
127+ require(it in androidTestAnnotationNames) {
128+ " Unsupported bitkitAndroidTestAnnotation '$it '. Supported annotations: " +
129+ androidTestAnnotationNames.joinToString(" , " )
130+ }
131+ }
132+ requestedAndroidTestAnnotationTaskName?.let { annotationName ->
133+ requestedAndroidTestAnnotation?.let {
134+ require(it == annotationName) {
135+ " Do not combine bitkitAndroidTestAnnotation '$it ' with generated lane '$annotationName '."
136+ }
137+ }
138+ }
139+ val bitkitAndroidTestAnnotationName = requestedAndroidTestAnnotation
140+ ? : requestedAndroidTestAnnotationTaskName
141+ val bitkitAndroidTestAnnotation = bitkitAndroidTestAnnotationName?.let {
142+ " $androidTestAnnotationPackage .$it "
143+ }
53144
54145android {
55146 namespace = " to.bitkit"
@@ -61,6 +152,9 @@ android {
61152 versionCode = 181
62153 versionName = " 2.2.0"
63154 testInstrumentationRunner = " to.bitkit.test.HiltTestRunner"
155+ bitkitAndroidTestAnnotation?.let {
156+ testInstrumentationRunnerArguments[" annotation" ] = it
157+ }
64158 vectorDrawables {
65159 useSupportLibrary = true
66160 }
@@ -367,4 +461,12 @@ tasks.withType<Test>().configureEach {
367461 jvmArgs(" -XX:+EnableDynamicAgentLoading" )
368462}
369463
464+ androidTestAnnotationNames.forEach { annotationName ->
465+ tasks.register(androidTestTaskName(annotationName)) {
466+ group = " verification"
467+ description = " Runs devDebug Android tests annotated with '$annotationName '."
468+ dependsOn(" connectedDevDebugAndroidTest" )
469+ }
470+ }
471+
370472// endregion
0 commit comments