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:
- '**.md'
jobs:
build:
Build:
runs-on: ubuntu-latest
steps:
- name: Check out source code
@ -18,39 +18,42 @@ jobs:
with:
java-version: 1.8
- name: Build & Run small tests
- name: Build Project
run: ./build.sh build
- name: Upload APK
- name: Upload Build Artifacts
uses: actions/upload-artifact@v2
with:
name: debug-apk
path: uhabits-android/build/*apk
- name: Upload build outputs
uses: actions/upload-artifact@v2
with:
name: build
name: uhabits-android
path: uhabits-android/build/outputs/
test:
needs: build
runs-on: macOS-latest
AndroidTest:
needs: Build
runs-on: macOS-10.15
strategy:
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:
- name: Check out source code
uses: actions/checkout@v1
- name: Download previous build folder
- name: Download Previously Built APK
uses: actions/download-artifact@v2
with:
name: build
name: uhabits-android
path: uhabits-android/build/outputs/
- name: Run medium tests
uses: ReactiveCircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
script: ./build.sh medium-tests
- name: Run Android Tests
run: ./build.sh android-tests ${{ matrix.api }}

@ -18,19 +18,24 @@
cd "$(dirname "$0")" || exit
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"
AVDNAME="uhabitsTest"
EMULATOR="${ANDROID_HOME}/tools/emulator"
GRADLE="./gradlew --stacktrace --quiet"
PACKAGE_NAME=org.isoron.uhabits
ANDROID_OUTPUTS_DIR="uhabits-android/build/outputs"
VERSION=$(grep VERSION_NAME gradle.properties | sed -e 's/.*=//g;s/ //g')
SDKMANAGER="${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager"
VERSION=$(grep versionName uhabits-android/build.gradle.kts | sed -e 's/.*"\([^"]*\)".*/\1/g')
if [ ! -f "${ANDROID_HOME}/platform-tools/adb" ]; then
echo "Error: ANDROID_HOME is not set correctly"
if [ -z $VERSION ]; then
echo "Could not parse app version from: uhabits-android/build.gradle.kts"
exit 1
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
# -----------------------------------------------------------------------------
@ -52,30 +57,91 @@ fail() {
exit 1
}
# Core
# -----------------------------------------------------------------------------
ktlint() {
log_info "Running ktlint..."
$GRADLE ktlintCheck || fail
}
build_core() {
core_build() {
log_info "Building uhabits-core..."
$GRADLE ktlintCheck || fail
$GRADLE :uhabits-core:build || fail
}
# Android
# -----------------------------------------------------------------------------
run_adb_as_root() {
log_info "Running adb as root..."
$ADB root
# shellcheck disable=SC2016
_setup_android_emulator() {
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
}
build_apk() {
_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
}
_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
log_info "Reading secret..."
# shellcheck disable=SC1091
@ -99,9 +165,7 @@ build_apk() {
cp -v \
uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk \
uhabits-android/build/loop-"$VERSION"-debug.apk
}
build_instrumentation_apk() {
log_info "Building instrumentation APK..."
if [ -n "$RELEASE" ]; then
$GRADLE :uhabits-android:assembleAndroidTest \
@ -114,126 +178,31 @@ build_instrumentation_apk() {
fi
}
uninstall_apk() {
log_info "Uninstalling existing APK..."
$ADB uninstall ${PACKAGE_NAME}
}
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
}
android_test() {
API=$1
_setup_android_emulator $API || return 1
_install_apks || return 1
_run_instrumented_tests || return 1
uninstall_test_apk() {
log_info "Uninstalling test APK..."
$ADB uninstall ${PACKAGE_NAME}.test
return 0
}
fetch_images() {
android_fetch_images() {
log_info "Fetching images"
rm -rf ${ANDROID_OUTPUTS_DIR}/test-screenshots
$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/
}
accept_images() {
android_accept_images() {
find ${ANDROID_OUTPUTS_DIR}/test-screenshots -name '*.expected*' -delete
rsync -av ${ANDROID_OUTPUTS_DIR}/test-screenshots/ uhabits-android/src/androidTest/assets/
}
remove_avd() {
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
}
# General
# -----------------------------------------------------------------------------
parse_opts() {
_parse_opts() {
if ! OPTS="$(getopt -o r --long release -n 'build.sh' -- "$@")" ; then
exit 1;
fi
@ -247,86 +216,81 @@ parse_opts() {
done
}
remove_build_dirs() {
rm -rfv uhabits-core/build
rm -rfv uhabits-web/node_modules/upath/build
rm -rfv uhabits-web/node_modules/core-js/build
rm -rfv uhabits-web/build
rm -rfv uhabits-core-legacy/build
rm -rfv uhabits-server/build
_print_usage() {
cat <<END
CI/CD script for Loop Habit Tracker.
Usage:
build.sh build [options]
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/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/build
rm -rfv uhabits-core/.gradle
rm -rfv uhabits-core/build
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
}
main() {
case "$1" in
build)
shift; parse_opts "$@"
ktlint
build_core
build_android
shift; _parse_opts "$@"
core_build
android_build
;;
medium-tests)
shift; parse_opts "$@"
for _ in {1..3}; do
(run_tests medium) && exit 0
done
exit 1
clean)
clean
;;
large-tests)
shift; parse_opts "$@"
stop_avd
remove_avd
for api in {28..28}; do
create_avd "$api"
run_avd
run_tests large
stop_avd
remove_avd
android-tests)
shift; _parse_opts "$@"
if [ -z $1 ]; then
_print_usage
exit 1
fi
for attempt in {1..3}; do
log_info "Running Android tests (attempt $attempt)..."
android_test $1 && return 0
done
log_error "Maximum number of attempts reached. Failing."
return 1
;;
fetch-images)
fetch_images
android-fetch-images)
android_fetch_images
;;
accept-images)
accept_images
android-accept-images)
android_accept_images
;;
clean)
remove_build_dirs
;;
*)
cat <<END
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
_print_usage
exit 1
;;
esac
}
main "$@"

@ -84,7 +84,6 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
androidTestImplementation("com.google.dagger:dagger:$daggerVersion")
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-jackson:$ktorVersion")
androidTestImplementation("androidx.annotation:annotation:1.2.0")

@ -24,7 +24,6 @@ import android.content.Intent
import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.linkedin.android.testbutler.TestButler
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences
@ -44,8 +43,6 @@ open class BaseUserInterfaceTest {
@Throws(Exception::class)
fun setUp() {
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
TestButler.setup(ApplicationProvider.getApplicationContext())
TestButler.verifyAnimationsDisabled(ApplicationProvider.getApplicationContext())
val app =
ApplicationProvider.getApplicationContext<Context>().applicationContext as HabitsApplication
component = app.component
@ -60,7 +57,6 @@ open class BaseUserInterfaceTest {
@Throws(Exception::class)
fun tearDown() {
for (i in 0..9) device.pressBack()
TestButler.teardown(ApplicationProvider.getApplicationContext())
}
@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.SETTINGS
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
@Ignore("Fails on GitHub Actions")
class LinksTest : BaseUserInterfaceTest() {
@Test
@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.dragCheckmarkWidgetToHomeScreen
import org.isoron.uhabits.acceptance.steps.WidgetSteps.verifyCheckmarkWidgetIsShown
import org.junit.Ignore
import org.junit.Test
@LargeTest
@Ignore("Flaky")
class WidgetTest : BaseUserInterfaceTest() {
@Test
@Throws(Exception::class)

Loading…
Cancel
Save