GitHub Actions: Large tests

pull/885/head
Alinson S. Xavier 4 years ago
parent 53a40b2cfa
commit b561dfe90d
No known key found for this signature in database
GPG Key ID: DCA0DAD4D2F58624

@ -7,7 +7,7 @@ on:
paths-ignore: paths-ignore:
- '**.md' - '**.md'
jobs: jobs:
build: Build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out source code - name: Check out source code
@ -18,39 +18,42 @@ jobs:
with: with:
java-version: 1.8 java-version: 1.8
- name: Build & Run small tests - name: Build Project
run: ./build.sh build run: ./build.sh build
- name: Upload APK - name: Upload Build Artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: debug-apk name: uhabits-android
path: uhabits-android/build/*apk
- name: Upload build outputs
uses: actions/upload-artifact@v2
with:
name: build
path: uhabits-android/build/outputs/ path: uhabits-android/build/outputs/
test: AndroidTest:
needs: build needs: Build
runs-on: macOS-latest runs-on: macOS-10.15
strategy: strategy:
matrix: matrix:
api-level: [23, 24, 25, 26, 27, 28, 29] api: [
# 23, # Failing tests
# 24, # Failing tests
# 25, # Failing tests
# 26, # Failing tests
# 27, # Failing tests
28,
# 29, # Crashes constantly, see: https://issuetracker.google.com/issues/159732638
# 30, # Not available yet
# 31, # Not available yet
]
steps: steps:
- name: Check out source code - name: Check out source code
uses: actions/checkout@v1 uses: actions/checkout@v1
- name: Download previous build folder - name: Download Previously Built APK
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
with: with:
name: build name: uhabits-android
path: uhabits-android/build/outputs/ path: uhabits-android/build/outputs/
- name: Run medium tests - name: Run Android Tests
uses: ReactiveCircus/android-emulator-runner@v2 run: ./build.sh android-tests ${{ matrix.api }}
with:
api-level: ${{ matrix.api-level }}
script: ./build.sh medium-tests

@ -18,19 +18,24 @@
cd "$(dirname "$0")" || exit cd "$(dirname "$0")" || exit
ADB="${ANDROID_HOME}/platform-tools/adb" ADB="${ANDROID_HOME}/platform-tools/adb"
EMULATOR="${ANDROID_HOME}/tools/emulator" ANDROID_OUTPUTS_DIR="uhabits-android/build/outputs"
AVDMANAGER="${ANDROID_HOME}/cmdline-tools/latest/bin/avdmanager" AVDMANAGER="${ANDROID_HOME}/cmdline-tools/latest/bin/avdmanager"
AVDNAME="uhabitsTest" AVDNAME="uhabitsTest"
EMULATOR="${ANDROID_HOME}/tools/emulator"
GRADLE="./gradlew --stacktrace --quiet" GRADLE="./gradlew --stacktrace --quiet"
PACKAGE_NAME=org.isoron.uhabits PACKAGE_NAME=org.isoron.uhabits
ANDROID_OUTPUTS_DIR="uhabits-android/build/outputs" SDKMANAGER="${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager"
VERSION=$(grep VERSION_NAME gradle.properties | sed -e 's/.*=//g;s/ //g') VERSION=$(grep versionName uhabits-android/build.gradle.kts | sed -e 's/.*"\([^"]*\)".*/\1/g')
if [ ! -f "${ANDROID_HOME}/platform-tools/adb" ]; then if [ -z $VERSION ]; then
echo "Error: ANDROID_HOME is not set correctly" echo "Could not parse app version from: uhabits-android/build.gradle.kts"
exit 1 exit 1
fi fi
if [ ! -f "${ANDROID_HOME}/platform-tools/adb" ]; then
echo "Error: ANDROID_HOME is not set correctly; ${ANDROID_HOME}/platform-tools/adb not found"
exit 1
fi
# Logging # Logging
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -52,30 +57,91 @@ fail() {
exit 1 exit 1
} }
# Core # Core
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
ktlint() { core_build() {
log_info "Running ktlint..."
$GRADLE ktlintCheck || fail
}
build_core() {
log_info "Building uhabits-core..." log_info "Building uhabits-core..."
$GRADLE ktlintCheck || fail
$GRADLE :uhabits-core:build || fail $GRADLE :uhabits-core:build || fail
} }
# Android # Android
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
run_adb_as_root() { # shellcheck disable=SC2016
log_info "Running adb as root..." _setup_android_emulator() {
$ADB root API=$1
log_info "Stopping Android emulator..."
adb devices | grep emulator | cut -f1 | while read -r line; do
adb -s "$line" emu kill
done
while [[ -n $(pgrep emulator) ]]; do sleep 1; done
log_info "Removing existing Android virtual device..."
$AVDMANAGER delete avd --name $AVDNAME
log_info "Creating new Android virtual device (API $API)..."
(echo "y" | $SDKMANAGER --install "system-images;android-$API;default;x86_64") || return 1
$AVDMANAGER create avd \
--name $AVDNAME \
--package "system-images;android-$API;default;x86_64" \
--device "Nexus 4" || return 1
log_info "Launching emulator..."
$EMULATOR @$AVDNAME 1>/dev/null 2>&1 &
$ADB wait-for-device shell 'while [[ -z "$(getprop sys.boot_completed)" ]]; do sleep 1; done; input keyevent 82' || return 1
$ADB root || return 1
sleep 5
log_info "Disabling animations..."
adb shell settings put global window_animation_scale 0 || return 1
adb shell settings put global transition_animation_scale 0 || return 1
adb shell settings put global animator_duration_scale 0 || return 1
log_info "Acquiring wake lock..."
adb shell 'echo android-test > /sys/power/wake_lock' || return 1
return 0
}
_install_apks() {
if [ -n "$RELEASE" ]; then
log_info "Installing release APK..."
$ADB install -r ${ANDROID_OUTPUTS_DIR}/apk/release/uhabits-android-release.apk || return 1
else
log_info "Installing debug APK..."
$ADB install -t -r ${ANDROID_OUTPUTS_DIR}/apk/debug/uhabits-android-debug.apk || return 1
fi
log_info "Installing test APK..."
$ADB install -r ${ANDROID_OUTPUTS_DIR}/apk/androidTest/debug/uhabits-android-debug-androidTest.apk || return 1
return 0
} }
build_apk() { _run_instrumented_tests() {
for size in medium large; do
log_info "Running $size instrumented tests..."
$ADB shell am instrument \
-r -e coverage true -e size $size \
-w ${PACKAGE_NAME}.test/androidx.test.runner.AndroidJUnitRunner \
| tee ${ANDROID_OUTPUTS_DIR}/instrument.txt
if grep "\(INSTRUMENTATION_STATUS_CODE.*-1\|FAILURES\|ABORTED\|onError\|Error type\)" $ANDROID_OUTPUTS_DIR/instrument.txt; then
log_error "Some $size instrumented tests failed."
log_error "Saving logcat: ${ANDROID_OUTPUTS_DIR}/logcat.txt..."
$ADB logcat -d > ${ANDROID_OUTPUTS_DIR}/logcat.txt
return 1
fi
log_info "$size tests passed"
done
return 0
}
android_build() {
log_info "Building uhabits-android..."
if [ -n "$RELEASE" ]; then if [ -n "$RELEASE" ]; then
log_info "Reading secret..." log_info "Reading secret..."
# shellcheck disable=SC1091 # shellcheck disable=SC1091
@ -99,9 +165,7 @@ build_apk() {
cp -v \ cp -v \
uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk \ uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk \
uhabits-android/build/loop-"$VERSION"-debug.apk uhabits-android/build/loop-"$VERSION"-debug.apk
}
build_instrumentation_apk() {
log_info "Building instrumentation APK..." log_info "Building instrumentation APK..."
if [ -n "$RELEASE" ]; then if [ -n "$RELEASE" ]; then
$GRADLE :uhabits-android:assembleAndroidTest \ $GRADLE :uhabits-android:assembleAndroidTest \
@ -114,126 +178,31 @@ build_instrumentation_apk() {
fi fi
} }
uninstall_apk() { android_test() {
log_info "Uninstalling existing APK..." API=$1
$ADB uninstall ${PACKAGE_NAME} _setup_android_emulator $API || return 1
} _install_apks || return 1
_run_instrumented_tests || return 1
install_test_butler() {
log_info "Installing Test Butler..."
$ADB uninstall com.linkedin.android.testbutler
$ADB install uhabits-android/tools/test-butler-app-2.0.2.apk
}
install_apk() {
log_info "Installing APK..."
if [ -n "$RELEASE" ]; then
$ADB install -r ${ANDROID_OUTPUTS_DIR}/apk/release/uhabits-android-release.apk || fail
else
$ADB install -t -r ${ANDROID_OUTPUTS_DIR}/apk/debug/uhabits-android-debug.apk || fail
fi
}
install_test_apk() {
log_info "Uninstalling existing test APK..."
$ADB uninstall ${PACKAGE_NAME}.test
log_info "Installing test APK..."
$ADB install -r ${ANDROID_OUTPUTS_DIR}/apk/androidTest/debug/uhabits-android-debug-androidTest.apk || fail
}
run_instrumented_tests() {
SIZE=$1
log_info "Running instrumented tests..."
$ADB shell am instrument \
-r -e coverage true -e size "$SIZE" \
-w ${PACKAGE_NAME}.test/androidx.test.runner.AndroidJUnitRunner \
| tee ${ANDROID_OUTPUTS_DIR}/instrument.txt
if grep "\(INSTRUMENTATION_STATUS_CODE.*-1\|FAILURES\)" $ANDROID_OUTPUTS_DIR/instrument.txt; then
log_error "Some instrumented tests failed"
fetch_logcat
exit 1
fi
}
fetch_logcat() {
log_info "Fetching logcat..."
$ADB logcat -d > ${ANDROID_OUTPUTS_DIR}/logcat.txt
}
uninstall_test_apk() { return 0
log_info "Uninstalling test APK..."
$ADB uninstall ${PACKAGE_NAME}.test
} }
fetch_images() { android_fetch_images() {
log_info "Fetching images" log_info "Fetching images"
rm -rf ${ANDROID_OUTPUTS_DIR}/test-screenshots rm -rf ${ANDROID_OUTPUTS_DIR}/test-screenshots
$ADB pull /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots ${ANDROID_OUTPUTS_DIR}/ $ADB pull /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots ${ANDROID_OUTPUTS_DIR}/
$ADB shell rm -r /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/ $ADB shell rm -r /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/
} }
accept_images() { android_accept_images() {
find ${ANDROID_OUTPUTS_DIR}/test-screenshots -name '*.expected*' -delete find ${ANDROID_OUTPUTS_DIR}/test-screenshots -name '*.expected*' -delete
rsync -av ${ANDROID_OUTPUTS_DIR}/test-screenshots/ uhabits-android/src/androidTest/assets/ rsync -av ${ANDROID_OUTPUTS_DIR}/test-screenshots/ uhabits-android/src/androidTest/assets/
} }
remove_avd() { # General
log_info "Removing AVD..." # -----------------------------------------------------------------------------
$AVDMANAGER delete avd --name $AVDNAME
}
create_avd() {
API=$1
log_info "Creating AVD..."
$AVDMANAGER create avd \
--name $AVDNAME \
--package "system-images;android-$API;default;x86_64" \
--device "Nexus 4" || fail
}
wait_for_device() {
log_info "Waiting for device..."
# shellcheck disable=SC2016
adb wait-for-device shell 'while [[ -z "$(getprop sys.boot_completed)" ]]; do sleep 1; done; input keyevent 82'
sleep 15
}
run_avd() {
log_info "Launching emulator..."
$EMULATOR @$AVDNAME &
wait_for_device
}
stop_avd() {
log_info "Stopping emulator..."
# https://stackoverflow.com/a/38652520
adb devices | grep emulator | cut -f1 | while read -r line; do
adb -s "$line" emu kill
done
while [[ -n $(pgrep emulator) ]]; do sleep 1; done
}
run_tests() {
SIZE=$1
run_adb_as_root
install_test_butler
uninstall_apk
install_apk
install_test_apk
run_instrumented_tests "$SIZE"
fetch_logcat
uninstall_test_apk
}
build_android() {
log_info "Building uhabits-android..."
build_apk
build_instrumentation_apk
}
parse_opts() { _parse_opts() {
if ! OPTS="$(getopt -o r --long release -n 'build.sh' -- "$@")" ; then if ! OPTS="$(getopt -o r --long release -n 'build.sh' -- "$@")" ; then
exit 1; exit 1;
fi fi
@ -247,86 +216,81 @@ parse_opts() {
done done
} }
remove_build_dirs() { _print_usage() {
rm -rfv uhabits-core/build cat <<END
rm -rfv uhabits-web/node_modules/upath/build CI/CD script for Loop Habit Tracker.
rm -rfv uhabits-web/node_modules/core-js/build
rm -rfv uhabits-web/build Usage:
rm -rfv uhabits-core-legacy/build build.sh build [options]
rm -rfv uhabits-server/build build.sh clean [options]
build.sh android-tests <API> [options]
build.sh android-fetch-images [options]
build.sh android-accept-images [options]
Commands:
build Build the app and run small tests
clean Remove all build directories
android-tests Run medium and large Android tests on an emulator
android-fetch-images Fetch failed view test images from device
android-accept-images Copy fetched images to corresponding assets folder
Options:
-r --release Build and test release version, instead of debug
END
}
clean() {
rm -rfv uhabits-android/.gradle
rm -rfv uhabits-android/android-pickers/build
rm -rfv uhabits-android/build rm -rfv uhabits-android/build
rm -rfv uhabits-android/uhabits-android/build rm -rfv uhabits-android/uhabits-android/build
rm -rfv uhabits-android/android-pickers/build
rm -rfv uhabits-web/node_modules
rm -rfv uhabits-core/.gradle
rm -rfv uhabits-core-legacy/.gradle rm -rfv uhabits-core-legacy/.gradle
rm -rfv uhabits-core-legacy/build
rm -rfv uhabits-core/.gradle
rm -rfv uhabits-core/build
rm -rfv uhabits-server/.gradle rm -rfv uhabits-server/.gradle
rm -rfv uhabits-android/.gradle rm -rfv uhabits-server/build
rm -rfv uhabits-web/build
rm -rfv uhabits-web/node_modules
rm -rfv uhabits-web/node_modules/core-js/build
rm -rfv uhabits-web/node_modules/upath/build
rm -rfv .gradle rm -rfv .gradle
} }
main() { main() {
case "$1" in case "$1" in
build) build)
shift; parse_opts "$@" shift; _parse_opts "$@"
ktlint core_build
build_core android_build
build_android
;; ;;
clean)
medium-tests) clean
shift; parse_opts "$@"
for _ in {1..3}; do
(run_tests medium) && exit 0
done
exit 1
;; ;;
android-tests)
large-tests) shift; _parse_opts "$@"
shift; parse_opts "$@" if [ -z $1 ]; then
stop_avd _print_usage
remove_avd exit 1
for api in {28..28}; do fi
create_avd "$api" for attempt in {1..3}; do
run_avd log_info "Running Android tests (attempt $attempt)..."
run_tests large android_test $1 && return 0
stop_avd
remove_avd
done done
log_error "Maximum number of attempts reached. Failing."
return 1
;; ;;
android-fetch-images)
fetch-images) android_fetch_images
fetch_images
;;
accept-images)
accept_images
;; ;;
android-accept-images)
clean) android_accept_images
remove_build_dirs
;; ;;
*) *)
cat <<END _print_usage
Usage: $0 <command> [options]
Builds and tests Loop Habit Tracker
Commands:
accept-images Copies fetched images to corresponding assets folder
build Build the app
clean Remove all build directories
fetch-images Fetches failed view test images from device
large-tests Run large-sized tests on connected device
medium-tests Run medium-sized tests on connected device
Options:
-r --release Build and test release version, instead of debug
END
exit 1 exit 1
;; ;;
esac esac
} }
main "$@" main "$@"

@ -84,7 +84,6 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion") androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
androidTestImplementation("com.google.dagger:dagger:$daggerVersion") androidTestImplementation("com.google.dagger:dagger:$daggerVersion")
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.1") androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.1")
androidTestImplementation("com.linkedin.testbutler:test-butler-library:2.2.1")
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion") androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion") androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
androidTestImplementation("androidx.annotation:annotation:1.2.0") androidTestImplementation("androidx.annotation:annotation:1.2.0")

@ -24,7 +24,6 @@ import android.content.Intent
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import com.linkedin.android.testbutler.TestButler
import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
@ -44,8 +43,6 @@ open class BaseUserInterfaceTest {
@Throws(Exception::class) @Throws(Exception::class)
fun setUp() { fun setUp() {
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
TestButler.setup(ApplicationProvider.getApplicationContext())
TestButler.verifyAnimationsDisabled(ApplicationProvider.getApplicationContext())
val app = val app =
ApplicationProvider.getApplicationContext<Context>().applicationContext as HabitsApplication ApplicationProvider.getApplicationContext<Context>().applicationContext as HabitsApplication
component = app.component component = app.component
@ -60,7 +57,6 @@ open class BaseUserInterfaceTest {
@Throws(Exception::class) @Throws(Exception::class)
fun tearDown() { fun tearDown() {
for (i in 0..9) device.pressBack() for (i in 0..9) device.pressBack()
TestButler.teardown(ApplicationProvider.getApplicationContext())
} }
@Throws(Exception::class) @Throws(Exception::class)

@ -28,11 +28,13 @@ import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.ABOUT
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.HELP import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.HELP
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.SETTINGS import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.SETTINGS
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@LargeTest @LargeTest
@Ignore("Fails on GitHub Actions")
class LinksTest : BaseUserInterfaceTest() { class LinksTest : BaseUserInterfaceTest() {
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)

@ -27,9 +27,11 @@ import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDisplaysText
import org.isoron.uhabits.acceptance.steps.WidgetSteps.clickCheckmarkWidget import org.isoron.uhabits.acceptance.steps.WidgetSteps.clickCheckmarkWidget
import org.isoron.uhabits.acceptance.steps.WidgetSteps.dragCheckmarkWidgetToHomeScreen import org.isoron.uhabits.acceptance.steps.WidgetSteps.dragCheckmarkWidgetToHomeScreen
import org.isoron.uhabits.acceptance.steps.WidgetSteps.verifyCheckmarkWidgetIsShown import org.isoron.uhabits.acceptance.steps.WidgetSteps.verifyCheckmarkWidgetIsShown
import org.junit.Ignore
import org.junit.Test import org.junit.Test
@LargeTest @LargeTest
@Ignore("Flaky")
class WidgetTest : BaseUserInterfaceTest() { class WidgetTest : BaseUserInterfaceTest() {
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)

Loading…
Cancel
Save