Skip to content

Commit 70d7f28

Browse files
authored
Merge pull request #4552 from EmmanuelMess/emmanuelmess/fix/emulator_ci
2 parents 9d4ffcc + eb43381 commit 70d7f28

7 files changed

Lines changed: 275 additions & 79 deletions

File tree

.github/workflows/android-build.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ jobs:
6060
echo "❌ .cargo/config.toml missing"
6161
exit 1
6262
fi
63+
- name: Setup Gradle
64+
uses: gradle/actions/setup-gradle@v5
6365
- name: Check formatting using spotless
64-
uses: gradle/actions/setup-gradle@v3
65-
with:
66-
arguments: spotlessCheck --stacktrace
66+
run: ./gradlew spotlessCheck --stacktrace
6767

6868
build:
6969
name: Build debug
@@ -108,7 +108,7 @@ jobs:
108108
echo "❌ .cargo/config.toml missing"
109109
exit 1
110110
fi
111+
- name: Setup Gradle
112+
uses: gradle/actions/setup-gradle@v5
111113
- name: Build with Gradle
112-
uses: gradle/actions/setup-gradle@v3
113-
with:
114-
arguments: assembledebug --stacktrace
114+
run: ./gradlew assembledebug --stacktrace

.github/workflows/android-feature.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ jobs:
2929
ndk-version: r28c
3030
link-to-sdk: true
3131
local-cache: true
32+
- name: Setup Gradle
33+
uses: gradle/actions/setup-gradle@v5
3234
- name: Check formatting using spotless
33-
uses: gradle/actions/setup-gradle@v3
34-
with:
35-
arguments: spotlessCheck --stacktrace
3635
env:
3736
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
37+
run: ./gradlew spotlessCheck --stacktrace
3838

3939
build:
4040
name: Build debug and run Jacoco tests
@@ -79,11 +79,9 @@ jobs:
7979
echo "❌ .cargo/config.toml missing"
8080
exit 1
8181
fi
82+
- name: Setup Gradle
83+
uses: gradle/actions/setup-gradle@v5
8284
- name: Build with Gradle
83-
uses: gradle/actions/setup-gradle@v3
84-
with:
85-
arguments: assembledebug --stacktrace
85+
run: ./gradlew assembledebug --stacktrace
8686
- name: Run test cases
87-
uses: gradle/actions/setup-gradle@v3
88-
with:
89-
arguments: jacocoTestPlayDebugUnitTestReport --stacktrace --info
87+
run: ./gradlew jacocoTestPlayDebugUnitTestReport --stacktrace --info

.github/workflows/android-main.yml

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ jobs:
2828
ndk-version: r28c
2929
link-to-sdk: true
3030
local-cache: true
31+
- name: Setup Gradle
32+
uses: gradle/actions/setup-gradle@v5
3133
- name: Check formatting using spotless
32-
uses: gradle/actions/setup-gradle@v3
33-
with:
34-
arguments: spotlessCheck
34+
run: ./gradlew spotlessCheck
3535

3636
build:
3737
name: Build debug, Jacoco test and publish to codacy
@@ -78,14 +78,12 @@ jobs:
7878
echo "❌ .cargo/config.toml missing"
7979
exit 1
8080
fi
81+
- name: Setup Gradle
82+
uses: gradle/actions/setup-gradle@v5
8183
- name: Build with Gradle
82-
uses: gradle/actions/setup-gradle@v3
83-
with:
84-
arguments: assembledebug
84+
run: ./gradlew assembledebug
8585
- name: Run test cases
86-
uses: gradle/actions/setup-gradle@v3
87-
with:
88-
arguments: jacocoTestPlayDebugUnitTestReport
86+
run: ./gradlew jacocoTestPlayDebugUnitTestReport
8987
- name: Publish test cases
9088
run: |
9189
export CODACY_PROJECT_TOKEN=${{ secrets.CODACY_TOKEN }}
@@ -153,8 +151,6 @@ jobs:
153151
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
154152
sudo udevadm control --reload-rules
155153
sudo udevadm trigger --name-match=kvm
156-
- name: Gradle cache
157-
uses: gradle/actions/setup-gradle@v3
158154
- name: AVD cache
159155
uses: actions/cache@v4
160156
id: avd-cache
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.amaze.filemanager.matcher
2+
3+
import android.content.Context
4+
import android.graphics.Bitmap
5+
import android.graphics.Canvas
6+
import android.graphics.drawable.Drawable
7+
import android.graphics.drawable.StateListDrawable
8+
import android.view.View
9+
import androidx.annotation.DrawableRes
10+
import androidx.appcompat.view.menu.ActionMenuItemView
11+
import androidx.test.espresso.matcher.BoundedMatcher
12+
import org.hamcrest.Description
13+
import org.hamcrest.Matcher
14+
15+
/**
16+
* This matcher allows to select by [ActionMenuItemView] icon
17+
*
18+
* From https://stackoverflow.com/a/70108466/3124150
19+
*/
20+
object ActionMenuIconMatcher {
21+
/**
22+
* A [Matcher] that will match against an [ActionMenuItemView] view with a specific icon
23+
* resource
24+
*/
25+
@JvmStatic
26+
fun withActionIconDrawable(
27+
@DrawableRes resourceId: Int,
28+
): Matcher<View?> {
29+
return object : BoundedMatcher<View?, ActionMenuItemView>(ActionMenuItemView::class.java) {
30+
override fun describeTo(description: Description) {
31+
description.appendText("has image drawable resource $resourceId")
32+
}
33+
34+
override fun matchesSafely(actionMenuItemView: ActionMenuItemView): Boolean {
35+
val iconDrawable = actionMenuItemView.itemData.icon ?: return false
36+
37+
return sameBitmap(
38+
actionMenuItemView.context,
39+
iconDrawable,
40+
resourceId,
41+
actionMenuItemView,
42+
)
43+
}
44+
}
45+
}
46+
47+
/**
48+
* Compares a [Drawable] against a resource id, returns if they are identical
49+
*/
50+
@JvmStatic
51+
private fun sameBitmap(
52+
context: Context,
53+
drawable: Drawable,
54+
@DrawableRes resourceId: Int,
55+
view: View,
56+
): Boolean {
57+
var drawable = drawable
58+
val otherDrawable: Drawable = context.resources.getDrawable(resourceId) ?: return false
59+
60+
if (drawable is StateListDrawable) {
61+
val getStateDrawableIndex =
62+
StateListDrawable::class.java.getMethod(
63+
"getStateDrawableIndex",
64+
IntArray::class.java,
65+
)
66+
val getStateDrawable =
67+
StateListDrawable::class.java.getMethod(
68+
"getStateDrawable",
69+
Int::class.javaPrimitiveType,
70+
)
71+
val index = getStateDrawableIndex.invoke(drawable, view.drawableState)
72+
drawable = getStateDrawable.invoke(drawable, index) as Drawable
73+
}
74+
75+
val bitmap = getBitmapFromDrawable(drawable)
76+
val otherBitmap = getBitmapFromDrawable(otherDrawable)
77+
return bitmap.sameAs(otherBitmap)
78+
}
79+
80+
/**
81+
* Convert a [Bitmap] to a [Drawable] by drawing to a canvas
82+
*/
83+
@JvmStatic
84+
private fun getBitmapFromDrawable(drawable: Drawable): Bitmap {
85+
val bitmap: Bitmap =
86+
Bitmap.createBitmap(
87+
drawable.intrinsicWidth,
88+
drawable.intrinsicHeight,
89+
Bitmap.Config.ARGB_8888,
90+
)
91+
val canvas = Canvas(bitmap)
92+
drawable.setBounds(0, 0, canvas.width, canvas.height)
93+
drawable.draw(canvas)
94+
return bitmap
95+
}
96+
}

app/src/androidTest/java/com/amaze/filemanager/ui/fragments/BackupPrefsFragmentTest.kt

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,27 @@ import android.content.SharedPreferences
2727
import android.net.Uri
2828
import android.os.Build.VERSION.SDK_INT
2929
import android.os.Build.VERSION_CODES.TIRAMISU
30+
import android.util.Log
3031
import androidx.lifecycle.Lifecycle
3132
import androidx.preference.PreferenceManager
3233
import androidx.test.core.app.ActivityScenario
3334
import androidx.test.core.app.ApplicationProvider
3435
import androidx.test.espresso.Espresso.onView
35-
import androidx.test.espresso.action.ViewActions
36+
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
37+
import androidx.test.espresso.NoMatchingViewException
38+
import androidx.test.espresso.action.ViewActions.click
39+
import androidx.test.espresso.assertion.ViewAssertions.matches
40+
import androidx.test.espresso.matcher.ViewMatchers
41+
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
3642
import androidx.test.espresso.matcher.ViewMatchers.withId
3743
import androidx.test.espresso.matcher.ViewMatchers.withText
3844
import androidx.test.ext.junit.runners.AndroidJUnit4
45+
import androidx.test.platform.app.InstrumentationRegistry
3946
import androidx.test.rule.GrantPermissionRule
4047
import com.amaze.filemanager.R
48+
import com.amaze.filemanager.matcher.ActionMenuIconMatcher.withActionIconDrawable
4149
import com.amaze.filemanager.test.StoragePermissionHelper
50+
import com.amaze.filemanager.ui.activities.MainActivity
4251
import com.amaze.filemanager.ui.activities.PreferencesActivity
4352
import com.amaze.filemanager.ui.fragments.preferencefragments.BackupPrefsFragment
4453
import com.google.gson.GsonBuilder
@@ -55,7 +64,6 @@ import java.io.File
5564

5665
@RunWith(AndroidJUnit4::class)
5766
class BackupPrefsFragmentTest {
58-
var storagePath = "/storage/emulated/0"
5967
var fileName = "amaze_backup.json"
6068

6169
@Rule
@@ -86,6 +94,17 @@ class BackupPrefsFragmentTest {
8694
fun testPreferencesExportImport() {
8795
val context = ApplicationProvider.getApplicationContext<Context>()
8896

97+
// Get the device's home path
98+
val activityScenario = ActivityScenario.launch(MainActivity::class.java)
99+
activityScenario.moveToState(Lifecycle.State.RESUMED)
100+
101+
lateinit var storagePath: String
102+
activityScenario.onActivity { mainActivity ->
103+
storagePath = mainActivity.getStorageDirectories()[0].path
104+
}
105+
106+
Log.i(BackupPrefsFragmentTest::class.java.simpleName, "File $storagePath")
107+
89108
val exportFile = File("$storagePath${File.separator}$fileName")
90109
exportFile.delete() // delete if already exists
91110

@@ -100,25 +119,32 @@ class BackupPrefsFragmentTest {
100119
context: Context,
101120
exportFile: File,
102121
) {
103-
val backupPrefsFragment = BackupPrefsFragment()
104122
val activityScenario = ActivityScenario.launch(PreferencesActivity::class.java)
123+
activityScenario.moveToState(Lifecycle.State.RESUMED)
105124

106-
activityScenario.moveToState(Lifecycle.State.STARTED)
107-
108-
activityScenario.onActivity {
109-
it.supportFragmentManager.beginTransaction()
110-
.add(backupPrefsFragment, null)
111-
.commitNow()
112-
113-
backupPrefsFragment.exportPrefs()
114-
}
125+
onView(withText(R.string.backup)).perform(click())
126+
onView(withText(R.string.pref_export)).perform(click())
115127

116128
val tempFile = File("${context.cacheDir.absolutePath}${File.separator}$fileName")
117129

118130
assertTrue(tempFile.exists())
119131

120-
onView(withId(R.id.home)).perform(ViewActions.click())
121-
onView(withText(R.string.save)).perform(ViewActions.click())
132+
try {
133+
// HACK to be able to open the overflow on smaller devices, but not on larger devices,
134+
// as the overflow menu would hide the home button on larger devices
135+
136+
onView(withId(R.id.home))
137+
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
138+
// Use icon if visible
139+
onView(withActionIconDrawable(R.drawable.ic_home_white_24dp)).perform(click())
140+
} catch (_: NoMatchingViewException) {
141+
// Open the menu first, to be able to select the home button on smaller devices
142+
openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation().targetContext)
143+
// Use text if visible
144+
onView(withText(R.string.home)).perform(click())
145+
}
146+
147+
onView(withText(R.string.save)).perform(click())
122148

123149
assertTrue(exportFile.exists())
124150

@@ -172,8 +198,6 @@ class BackupPrefsFragmentTest {
172198
.add(backupPrefsFragment, null)
173199
.commitNow()
174200

175-
javaClass.getResourceAsStream("/$fileName")?.copyTo(exportFile.outputStream())
176-
177201
backupPrefsFragment.onActivityResult(
178202
BackupPrefsFragment.IMPORT_BACKUP_FILE,
179203
Activity.RESULT_OK,

0 commit comments

Comments
 (0)