Merge pull request #1641 from kiwix/feature/macgills/1593-github-actions

#1593 Move CI from Travis to GitHub Actions - replace travis with actions
This commit is contained in:
Seán Mac Gillicuddy 2020-01-06 10:31:50 +00:00 committed by GitHub
commit 7410ea640d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 270 additions and 153 deletions

38
.github/workflows/coverage.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: Coverage Reporting
on:
push:
branches:
- master
- develop
pull_request:
branches:
- '**'
jobs:
coverageReport:
strategy:
matrix:
api-level: [21, 29]
fail-fast: false
runs-on: macOS-latest
steps:
- name: checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
- name: create instrumentation coverage
uses: reactivecircus/android-emulator-runner@v2.0.0
with:
api-level: ${{ matrix.api-level }}
arch: x86_64
script: bash instrumentation.sh
- name: create unit coverage
run: ./gradlew jacocoTestDebugUnitTestReport jacocoTestCustomExampleDebugUnitTestReport
- name: upload coverage
run: bash <(curl -s https://codecov.io/bash)

60
.github/workflows/nightly.yml vendored Normal file
View File

@ -0,0 +1,60 @@
name: Nightly
on:
schedule:
# every night at midnight
- cron: '0 0 * * *'
jobs:
instrumentation_tests:
strategy:
matrix:
api-level: [21, 22, 23, 24, 25, 27, 28, 29]
fail-fast: false
runs-on: macOS-latest
steps:
- name: checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
- name: run instrumentation tests
uses: reactivecircus/android-emulator-runner@v2.0.0
with:
api-level: ${{ matrix.api-level }}
arch: x86_64
script: ./gradlew connectedDebugAndroidTest
unit_test_and_release:
needs: instrumentation_tests
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
- name: run unit tests
run: ./gradlew testDebugUnitTest testCustomExampleDebugUnitTest
- name: build debug
run: ./gradlew assembleDebug
- name: Decrypt files
env:
ssh_key: ${{ secrets.ssh_key }}
run: |
echo "$ssh_key" | base64 -d > ssh_key
- name: release debug to kiwix.download.org
env:
UNIVERSAL_DEBUG_APK: app/build/outputs/apk/debug/*universal*.apk
DATE: $(echo $(date +%Y-%m-%d))
run: |
mkdir $DATE
cp $UNIVERSAL_DEBUG_APK $DATE
scp -vrp -i ssh_key -o StrictHostKeyChecking=no $DATE ci@download.kiwix.org:/data/download/nightly/

29
.github/workflows/pull_request.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Pull requests
on: [pull_request]
jobs:
staticAnalysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Static Analysis
run: ./gradlew ktlintCheck app:lintDebug core:lintDebug custom:lintCustomexampleDebug
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Build all configurations
run: ./gradlew assemble

66
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: Publish App
on:
push:
branches-ignore:
- '*'
- '*/**'
tags:
- '*'
- '*/**'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Decrypt files
env:
keystore: ${{ secrets.keystore }}
google_json: ${{ secrets.google_json }}
ssh_key: ${{ secrets.ssh_key }}
run: |
echo "$google_json" | base64 -d > google.json
echo "$keystore" | base64 -d > kiwix-android.keystore
echo "$ssh_key" | base64 -d > ssh_key
- name: Publish app to play store
env:
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
run: ./gradlew publishReleaseApk
- name: Publish app to download.kiwix.org
env:
UNIVERSAL_RELEASE_APK: app/build/outputs/apk/release/*universal*.apk
TAG: $(echo ${GITHUB_REF:10})
OUTPUT_NAME: kiwix-$TAG.apk
run: |
cp $UNIVERSAL_RELEASE_APK $OUTPUT_NAME
scp -vrp -i ssh_key -o StrictHostKeyChecking=no $OUTPUT_NAME ci@download.kiwix.org:/data/download/release/kiwix-android/
- name: Create Github Release
id: create_release
uses: actions/create-release@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: true
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OUTPUT_DIR: ${{app/build/outputs/apk}}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: $OUTPUT_DIR
asset_name: assets.zip
asset_content_type: application/vnd.android.package-archive

View File

@ -1,101 +0,0 @@
language: android
jdk: oraclejdk8
sudo: required
dist: trusty
env:
global:
# switch glibc to a memory conserving mode
- MALLOC_ARENA_MAX=2
- ANDROID_TARGET=android-19
- ANDROID_ABI=armeabi-v7a
if: type != push OR tag IS present
install:
- pip install --user 'requests[security]'
- wget -r -nH -nd -np -R index.html* robots.txt* http://download.kiwix.org/dev/android/api/licenses/ -e robots=off -P $ANDROID_HOME/licenses || true
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- "$HOME/.gradle/caches/"
- "$HOME/.gradle/wrapper/"
- "$HOME/.android/build-cache"
android:
components:
- build-tools-28.0.3
- android-28
- extra-android-m2repository
- $ANDROID_TARGET
- sys-img-${ANDROID_ABI}-${ANDROID_TARGET}
licenses:
- ".+"
before_script:
- chmod +x gradlew
- echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI -c 100M
- emulator -avd test -no-audio -no-window &
- android-wait-for-emulator
- adb shell input keyevent 82 &
script:
- ./gradlew jacocoInstrumentationTestReport ktlintCheck app:lintDebug core:lintDebug custom:lintCustomexampleDebug jacocoTestDebugUnitTestReport jacocoTestCustomExampleDebugUnitTestReport app:assembleDebug
after_success:
- bash <(curl -s https://codecov.io/bash)
- openssl aes-256-cbc -K $encrypted_82adfa9c3806_key -iv $encrypted_82adfa9c3806_iv -in secrets.tar.enc -out secrets.tar -d
- tar xvf secrets.tar
- ./gradlew app:assembleRelease
before_deploy:
# - export APP_CHANGELOG=$(cat app/src/kiwix/play/release-notes/en-US/default.txt)
- export DATE=$(date +%Y-%m-%d)
- export OUTPUT_DIR=app/build/outputs/apk
- export UNIVERSAL_RELEASE_APK=$OUTPUT_DIR/release/*universal*.apk
- export UNIVERSAL_DEBUG_APK=$OUTPUT_DIR/debug/*universal*.apk
- export SSH_KEY=travisci_builder_id_key
- chmod 600 $SSH_KEY
deploy:
#publish on github releases
- provider: releases
api_key: "$GITHUB_TOKEN"
file: $OUTPUT_DIR/release/*
file_glob: true
skip_cleanup: true
overwrite: true
#body: "$APP_CHANGELOG" broken because travis can't escape newlines https://github.com/travis-ci/dpl/issues/155
draft: true
on:
tags: true
#publish on play store
- provider: script
skip_cleanup: true
script: ./gradlew publishReleaseApk
on:
tags: true
#publish release on download.kiwix.org
- provider: script
skip_cleanup: true
script: mkdir $TRAVIS_TAG && cp $UNIVERSAL_RELEASE_APK $TRAVIS_TAG && scp -vrp -i ${SSH_KEY} -o StrictHostKeyChecking=no $TRAVIS_TAG ci@download.kiwix.org:/data/download/release/kiwix-android/
on:
tags: true
#publish debug nightly on download.kiwix.org
- provider: script
skip_cleanup: true
script: mkdir $DATE && cp $UNIVERSAL_DEBUG_APK $DATE && scp -vrp -i ${SSH_KEY} -o StrictHostKeyChecking=no $DATE ci@download.kiwix.org:/data/download/nightly/
on:
branch: develop

View File

@ -23,6 +23,7 @@ import android.widget.TextView
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import androidx.test.runner.lifecycle.Stage.RESUMED
import androidx.viewpager.widget.ViewPager
import org.kiwix.kiwixmobile.BaseRobot
import java.io.File
@ -54,6 +55,13 @@ fun combineMessages(
fun getViewHierarchy(v: View) =
StringBuilder().apply { getViewHierarchy(v, this, 0) }.toString()
fun attempt(count: Int, function: () -> Unit): Unit = try {
function.invoke()
} catch (e: Exception) {
if (count - 1 == 0) throw e
else attempt(count - 1, function)
}
private fun getViewHierarchy(v: View, desc: StringBuilder, margin: Int) {
desc.append(getViewMessage(v, margin))
if (v is ViewGroup) {
@ -65,7 +73,9 @@ private fun getViewHierarchy(v: View, desc: StringBuilder, margin: Int) {
private fun getViewMessage(v: View, marginOffset: Int) =
"${numSpaces(marginOffset)}[${v.javaClass.simpleName}]${resourceId(v)}${text(v)}" +
"${contentDescription(v)}${visibility(v)}\n"
"${contentDescription(v)}${visibility(v)}${page(v)}\n"
fun page(v: View) = if (v is ViewPager) " page: ${v.currentItem}" else ""
fun visibility(v: View) = " visibility:" +
when (v.visibility) {

View File

@ -30,7 +30,9 @@ import org.kiwix.kiwixmobile.Findable.StringId.ContentDesc
import org.kiwix.kiwixmobile.Findable.Text
import org.kiwix.kiwixmobile.Findable.ViewId
const val WAIT_TIMEOUT_MS = 10000L
const val DEFAULT_WAIT = 10_000L
const val LONG_WAIT = 20_000L
const val VERY_LONG_WAIT = 40_000L
abstract class BaseRobot(
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
@ -50,7 +52,7 @@ abstract class BaseRobot(
uiDevice.pressBack()
}
protected fun isVisible(findable: Findable, timeout: Long = WAIT_TIMEOUT_MS) =
protected fun isVisible(findable: Findable, timeout: Long = DEFAULT_WAIT) =
waitFor(findable, timeout) ?: throw RuntimeException(findable.errorMessage(this))
protected fun UiObject2.swipeLeft() {
@ -61,7 +63,7 @@ abstract class BaseRobot(
customSwipe(Direction.RIGHT)
}
protected fun clickOn(findable: Findable, timeout: Long = WAIT_TIMEOUT_MS) {
protected fun clickOn(findable: Findable, timeout: Long = DEFAULT_WAIT) {
isVisible(findable, timeout).click()
}
@ -70,7 +72,7 @@ abstract class BaseRobot(
}
protected fun clickOnTab(textId: Int) {
clickOn(ContentDesc(textId))
clickOn(ContentDesc(textId), LONG_WAIT)
}
protected fun waitFor(milliseconds: Long) {
@ -79,14 +81,14 @@ abstract class BaseRobot(
private fun waitFor(
findable: Findable,
timeout: Long = WAIT_TIMEOUT_MS
timeout: Long = DEFAULT_WAIT
): UiObject2? =
uiDevice.wait(Until.findObject(findable.selector(this)), timeout)
private fun UiObject2.customSwipe(
direction: Direction,
fl: Float = 1.0f
percent: Float = 0.8f
) {
swipe(direction, fl)
swipe(direction, percent)
}
}

View File

@ -25,7 +25,6 @@ import okhttp3.mockwebserver.RecordedRequest
import org.kiwix.sharedFunctions.TEST_PORT
import org.simpleframework.xml.core.Persister
import java.io.StringWriter
import java.util.Stack
class KiwixMockServer {
@ -63,10 +62,6 @@ class KiwixMockServer {
forcedResponse = mockResponse
}
private fun <E> Stack<E>.popOrNull() =
if (empty()) null
else pop()
private fun Any.asXmlString() = StringWriter().let {
Persister().write(this, it)
"$it"

View File

@ -31,7 +31,6 @@ class IntroActivityTest : BaseActivityTest<IntroActivity>() {
fun viewIsSwipeableAndNavigatesToMain() {
intro {
swipeLeft()
swipeRight()
} clickGetStarted { }
}
}

View File

@ -18,14 +18,17 @@
package org.kiwix.kiwixmobile.intro
import applyWithViewHierarchyPrinting
import attempt
import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.Findable.StringId.TextId
import org.kiwix.kiwixmobile.Findable.ViewId
import org.kiwix.kiwixmobile.LONG_WAIT
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.main.MainRobot
import org.kiwix.kiwixmobile.main.main
fun intro(func: IntroRobot.() -> Unit) = IntroRobot().apply(func)
fun intro(func: IntroRobot.() -> Unit) = IntroRobot().applyWithViewHierarchyPrinting(func)
class IntroRobot : BaseRobot() {
@ -33,19 +36,17 @@ class IntroRobot : BaseRobot() {
private val viewPager = ViewId(R.id.view_pager)
init {
isVisible(getStarted)
isVisible(getStarted, LONG_WAIT)
isVisible(TextId(R.string.welcome_to_the_family))
isVisible(TextId(R.string.human_kind_knowledge))
}
fun swipeLeft() {
isVisible(viewPager).swipeLeft()
isVisible(TextId(R.string.save_books_offline))
isVisible(TextId(R.string.download_books_message))
}
fun swipeRight() {
isVisible(viewPager).swipeRight()
isVisible(TextId(R.string.welcome_to_the_family))
isVisible(TextId(R.string.human_kind_knowledge))
attempt(10) {
isVisible(viewPager).swipeLeft()
isVisible(TextId(R.string.save_books_offline))
isVisible(TextId(R.string.download_books_message))
}
}
infix fun clickGetStarted(func: MainRobot.() -> Unit): MainRobot {

View File

@ -25,7 +25,6 @@ import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import com.schibsted.spain.barista.interaction.BaristaMenuClickInteractions;
import com.schibsted.spain.barista.interaction.BaristaSleepInteractions;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -56,8 +55,6 @@ public class MainActivityTest {
}
@Test
@Ignore("This is hanging on travis")
//TODO fix as part of https://github.com/kiwix/kiwix-android/issues/1428
public void navigateSettings() {
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
enterSettings();

View File

@ -20,7 +20,7 @@ package org.kiwix.kiwixmobile.settings;
import android.preference.Preference;
import androidx.test.rule.ActivityTestRule;
import org.junit.Ignore;
import org.jetbrains.annotations.NotNull;
import org.junit.Rule;
import org.junit.Test;
import org.kiwix.kiwixmobile.core.R;
@ -35,7 +35,6 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.kiwix.kiwixmobile.utils.StandardActions.enterSettings;
@Ignore("This is hanging the build") //TODO convert to Kotlin/PageObject
public class KiwixSettingsActivityTest {
@Rule
public ActivityTestRule<KiwixMainActivity> activityTestRule =
@ -44,15 +43,6 @@ public class KiwixSettingsActivityTest {
@Test
public void testToggle() {
enterSettings();
onData(allOf(
is(instanceOf(Preference.class)),
withKey("pref_nightmode")))
.perform(click());
onData(allOf(
is(instanceOf(Preference.class)),
withKey("pref_auto_nightmode")))
.perform(click());
onData(allOf(
is(instanceOf(Preference.class)),
@ -138,5 +128,24 @@ public class KiwixSettingsActivityTest {
assertDisplayed(R.string.clear_all_history_dialog_title);
}
@Test
public void testNightModeDialog() {
enterSettings();
onData(allOf(
is(instanceOf(Preference.class)),
withKey("pref_night_mode")))
.perform(click());
for (String nightModeString : nightModeStrings()) {
assertDisplayed(nightModeString);
}
}
@NotNull private String[] nightModeStrings() {
return activityTestRule.getActivity()
.getResources()
.getStringArray(R.array.pref_night_modes_entries);
}
}

View File

@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.zim_manager
import android.os.Build
import androidx.test.filters.SdkSuppress
import attempt
import okhttp3.mockwebserver.MockResponse
import org.junit.Test
import org.kiwix.kiwixmobile.BaseActivityTest
@ -59,8 +60,11 @@ class ZimManageActivityTest : BaseActivityTest<ZimManageActivity>() {
searchFor(book)
pressBack()
pressBack()
forceResponse("0123456789")
clickOn(book)
forceResponse("012345678901234567890123456789012345678901234567890123456789012345678")
attempt(10) {
clickOn(book)
waitForEmptyView()
}
}
clickOnDownloading {
clickStop()
@ -94,9 +98,7 @@ class ZimManageActivityTest : BaseActivityTest<ZimManageActivity>() {
mockServer.forceResponse(
MockResponse()
.setBody(body)
.throttleBody(
1L, 1L, SECONDS
)
.throttleBody(1L, 1L, SECONDS)
)
}

View File

@ -22,7 +22,9 @@ import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.Findable.StringId.TextId
import org.kiwix.kiwixmobile.Findable.Text
import org.kiwix.kiwixmobile.Findable.ViewId
import org.kiwix.kiwixmobile.LONG_WAIT
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.VERY_LONG_WAIT
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.language.LanguageRobot
import org.kiwix.kiwixmobile.language.language
@ -32,7 +34,7 @@ fun zimManage(func: ZimManageRobot.() -> Unit) =
class ZimManageRobot : BaseRobot() {
init {
isVisible(ViewId(R.id.manageViewPager))
isVisible(ViewId(R.id.manageViewPager), VERY_LONG_WAIT)
}
fun clickOnOnline(func: LibraryRobot.() -> Unit): LibraryRobot {
@ -67,7 +69,7 @@ class ZimManageRobot : BaseRobot() {
}
fun clickOnSearch() {
clickOn(ViewId(R.id.action_search))
clickOn(ViewId(R.id.action_search), LONG_WAIT)
}
fun searchFor(book: Book) {
@ -75,18 +77,18 @@ class ZimManageRobot : BaseRobot() {
}
fun waitForEmptyView() {
isVisible(ViewId(R.id.libraryErrorText))
isVisible(ViewId(R.id.libraryErrorText), VERY_LONG_WAIT)
}
}
private fun download(func: DownloadRobot.() -> Unit) = DownloadRobot().apply(func)
inner class DownloadRobot : BaseRobot() {
init {
isVisible(ViewId(R.id.zim_download_root), 20000L)
isVisible(ViewId(R.id.zim_download_root))
}
fun clickStop() {
clickOn(ViewId(R.id.stop))
clickOn(ViewId(R.id.stop), LONG_WAIT)
}
fun waitForEmptyView() {

View File

@ -83,7 +83,7 @@ object Versions {
const val fragment_ktx: String = "1.1.0"
const val orchestrator: String = "1.1.0" // available: "1.2.0"
const val orchestrator: String = "1.2.0"
const val lint_gradle: String = "26.5.2"

View File

@ -26,6 +26,7 @@ import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat;
import io.reactivex.Flowable;
import io.reactivex.processors.PublishProcessor;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Inject;
@ -110,8 +111,10 @@ public class SharedPreferenceUtil {
public String getPrefStorage() {
String storage = sharedPreferences.getString(PREF_STORAGE, null);
if (storage == null) {
storage =
ContextCompat.getExternalFilesDirs(CoreApp.getInstance(), null)[0].getPath();
final File externalFilesDir =
ContextCompat.getExternalFilesDirs(CoreApp.getInstance(), null)[0];
storage = externalFilesDir != null ? externalFilesDir.getPath()
: CoreApp.getInstance().getFilesDir().getPath(); // workaround for emulators
putPrefStorage(storage);
}
return storage;

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

5
instrumentation.sh Normal file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
adb logcat -c
adb logcat *:E -v color &
./gradlew jacocoInstrumentationTestReport

Binary file not shown.