Skip to content

Commit

Permalink
Add new tests and convert some repo tests to local JVM tests
Browse files Browse the repository at this point in the history
Create a generic test suite for SyncRepo implementations in the
"shared-test" library. This allows writing implementation-agnostic test
cases which all implementations must pass.

The challenge in writing "abstract" tests for SyncRepo is that most
implementations can be tested with local unit tests, whereas
DocumentRepo requires instrumented tests, since DocumentFile is a
central class, and it's an Android class.
  • Loading branch information
amberin committed Aug 24, 2024
1 parent 292ccd8 commit d51b62d
Show file tree
Hide file tree
Showing 18 changed files with 1,451 additions and 281 deletions.
48 changes: 44 additions & 4 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,41 @@ on:
workflow_dispatch:

jobs:
test:

localUnitTests:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Setup Android SDK
uses: android-actions/setup-android@v3

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Gradle build Fdroid
run: ./gradlew assembleFdroidDebug

- name: Gradle test Fdroid
run: ./gradlew testFdroidDebugUnitTest

- name: Add Dropbox API credentials (for DropboxRepo tests)
shell: bash
run: |
echo "dropbox.refresh_token = \"${{ secrets.DROPBOX_REFRESH_TOKEN }}\"" >> app.properties
echo "dropbox.app_key = \"${{ secrets.DROPBOX_APP_KEY }}\"" >> app.properties
- name: Gradle test Premium
run: ./gradlew testPremiumDebugUnitTest

instrumentedTests:
runs-on: ubuntu-latest
strategy:
matrix:
Expand All @@ -39,8 +73,14 @@ jobs:
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4

- name: AVD cache
uses: actions/cache@v4
Expand Down Expand Up @@ -80,7 +120,7 @@ jobs:
echo "dropbox.refresh_token = \"${{ secrets.DROPBOX_REFRESH_TOKEN }}\"" >> app.properties
echo "dropbox.app_key = \"${{ secrets.DROPBOX_APP_KEY }}\"" >> app.properties
- name: Run tests
- name: Run instrumented tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
Expand All @@ -91,4 +131,4 @@ jobs:
disable-spellchecker: true
profile: Nexus 6
# Tests should use the build which includes Dropbox code.
script: ./gradlew connected${{matrix.flavor}}DebugAndroidTest --no-watch-fs --build-cache --info
script: ./gradlew --no-configuration-cache connected${{matrix.flavor}}DebugAndroidTest --no-watch-fs --build-cache --info
18 changes: 15 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ android {
viewBinding true
}

// testOptions {
// execution 'ANDROIDX_TEST_ORCHESTRATOR'
// }
testOptions {
unitTests {
includeAndroidResources = true
}
}

buildTypes {
release {
Expand Down Expand Up @@ -149,7 +151,15 @@ dependencies {

implementation "androidx.work:work-runtime-ktx:$versions.android_workmanager"

testImplementation "androidx.test:runner:$versions.android_test"
testImplementation "androidx.test:rules:$versions.android_test"
testImplementation "androidx.test.ext:junit:$versions.android_test_ext_junit"
testImplementation "junit:junit:$versions.junit"
testImplementation 'org.robolectric:robolectric:4.13'
testImplementation "io.github.atetzner:webdav-embedded-server:0.2.1"
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2")
testImplementation "org.mockito:mockito-core:2.28.2"
testImplementation "org.mockito.kotlin:mockito-kotlin:5.1.0"

// AndroidX Test
androidTestImplementation "androidx.test.espresso:espresso-core:$versions.android_test_espresso"
Expand Down Expand Up @@ -209,6 +219,8 @@ dependencies {
implementation("androidx.biometric:biometric-ktx:$versions.biometric_ktx") {
because 'Protect SSH key with biometric prompt'
}
testImplementation(project(":shared-test"))
androidTestImplementation(project(":shared-test"))
}

repositories {
Expand Down
2 changes: 0 additions & 2 deletions app/src/androidTest/java/com/orgzly/android/OrgzlyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import android.Manifest;
import android.app.Activity;
import android.app.UiAutomation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
Expand All @@ -20,7 +19,6 @@
import com.orgzly.android.repos.RepoFactory;
import com.orgzly.android.util.UserTimeFormatter;
import com.orgzly.org.datetime.OrgDateTime;
import com.orgzly.test.BuildConfig;

import org.junit.After;
import org.junit.Before;
Expand Down
9 changes: 9 additions & 0 deletions app/src/androidTest/java/com/orgzly/android/TestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,21 @@ public SyncRepo repoInstance(RepoType type, String url) {
return dataRepository.getRepoInstance(13, type, url);
}

public SyncRepo repoInstance(RepoType type, String url, Long id) {
return dataRepository.getRepoInstance(id, type, url);
}

public Repo setupRepo(RepoType type, String url) {
long id = dataRepository.createRepo(new RepoWithProps(new Repo(0, type, url)));

return dataRepository.getRepo(id);
}

public Repo setupRepo(RepoType type, String url, Map<String, String> props) {
long id = dataRepository.createRepo(new RepoWithProps(new Repo(0, type, url), props));
return dataRepository.getRepo(id);
}

public void deleteRepo(String url) {
Repo repo = dataRepository.getRepo(url);
if (repo != null) {
Expand Down
159 changes: 0 additions & 159 deletions app/src/androidTest/java/com/orgzly/android/espresso/SyncingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,29 +74,6 @@ public void testRunSync() {
sync();
}

@Test
public void testForceLoadingBookWithLink() {
Repo repo = testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
testUtils.setupRook(repo, "mock://repo-a/booky.org", "New content", "abc", 1234567890000L);
testUtils.setupBook("booky", "First book used for testing\n* Note A");
scenario = ActivityScenario.launch(MainActivity.class);

onView(allOf(withText("booky"), isDisplayed())).perform(longClick());
contextualToolbarOverflowMenu().perform(click());
onView(withText(R.string.books_context_menu_item_set_link)).perform(click());
onView(withText("mock://repo-a")).perform(click());

onView(allOf(withText("booky"), isDisplayed())).perform(longClick());
onView(withId(R.id.books_context_menu_force_load)).perform(click());
onView(withText(R.string.overwrite)).perform(click());
onBook(0, R.id.item_book_last_action)
.check(matches((withText(containsString(context.getString(R.string.force_loaded_from_uri, "mock://repo-a/booky.org"))))));

onView(allOf(withText("booky"), isDisplayed())).perform(click());
onView(allOf(withId(R.id.item_preface_text_view), withText("New content")))
.check(matches(isDisplayed()));
}

@Test
public void testAutoSyncIsTriggeredAfterCreatingNote() {
Repo repo = testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
Expand Down Expand Up @@ -169,64 +146,6 @@ public void nonLinkedBookCannotBeMadeOutOfSync() {
onBook(0, R.id.item_book_sync_needed_icon).check(matches(not(isDisplayed())));
}

@Test
public void testForceLoadingBookWithNoLinkNoRepos() {
testUtils.setupBook("booky", "First book used for testing\n* Note A");
testUtils.setupBook("book-two", "Second book used for testing\n* Note 1\n* Note 2");
scenario = ActivityScenario.launch(MainActivity.class);

onView(allOf(withText("booky"), isDisplayed())).perform(longClick());
onView(withId(R.id.books_context_menu_force_load)).perform(click());
onView(withText(R.string.overwrite)).perform(click());
onSnackbar().check(matches(withText(endsWith(context.getString(R.string.message_book_has_no_link)))));
}

@Test
public void testForceLoadingBookWithNoLinkSingleRepo() {
testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
testUtils.setupBook("booky", "First book used for testing\n* Note A");
testUtils.setupBook("book-two", "Second book used for testing\n* Note 1\n* Note 2");
scenario = ActivityScenario.launch(MainActivity.class);

onView(allOf(withText("booky"), isDisplayed())).perform(longClick());
onView(withId(R.id.books_context_menu_force_load)).perform(click());
onView(withText(R.string.overwrite)).perform(click());
onSnackbar().check(matches(withText(endsWith(context.getString(R.string.message_book_has_no_link)))));
}

/* Books view was returning multiple entries for the same book, due to duplicates in encodings
* table. The last statement in this method will fail if there are multiple books matching.
*/
@Test
public void testForceLoadingMultipleTimes() {
Repo repo = testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
testUtils.setupRook(repo, "mock://repo-a/book-one.org", "New content", "abc", 1234567890000L);
testUtils.setupBook("book-one", "First book used for testing\n* Note A");
testUtils.setupBook("book-two", "Second book used for testing\n* Note 1\n* Note 2");
scenario = ActivityScenario.launch(MainActivity.class);

onView(allOf(withText("book-one"), isDisplayed())).perform(longClick());
contextualToolbarOverflowMenu().perform(click());
onView(withText(R.string.books_context_menu_item_set_link)).perform(click());
onView(withText("mock://repo-a")).perform(click());

onView(allOf(withText("book-one"), isDisplayed())).perform(longClick());
onView(withId(R.id.books_context_menu_force_load)).perform(click());
onView(withText(R.string.overwrite)).perform(click());

onBook(0, R.id.item_book_last_action)
.check(matches(withText(endsWith(
context.getString(R.string.force_loaded_from_uri, "mock://repo-a/book-one.org")))));

onView(allOf(withText("book-one"), isDisplayed())).perform(longClick());
onView(withId(R.id.books_context_menu_force_load)).perform(click());
onView(withText(R.string.overwrite)).perform(click());

onBook(0, R.id.item_book_last_action)
.check(matches(withText(endsWith(
context.getString(R.string.force_loaded_from_uri, "mock://repo-a/book-one.org")))));
}

/*
* Book is left with out-of-sync icon when it's modified, then force-loaded.
* This is because book's mtime was not being updated and was greater then remote book's mtime.
Expand Down Expand Up @@ -296,69 +215,6 @@ public void testForceLoadingMultipleBooks() {
onNoteInBook(1, R.id.item_head_title_view).check(matches(withText("Note 1")));
}

@Test
public void testForceSavingBookWithNoLinkAndMultipleRepos() {
testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
testUtils.setupRepo(RepoType.MOCK, "mock://repo-b");
testUtils.setupBook("book-one", "First book used for testing\n* Note A");
testUtils.setupBook("book-two", "Second book used for testing\n* Note 1\n* Note 2");
scenario = ActivityScenario.launch(MainActivity.class);

onView(allOf(withText("book-one"), isDisplayed())).perform(longClick());
onView(withId(R.id.books_context_menu_force_save)).perform(click());
onView(withText(R.string.overwrite)).perform(click());

onBook(0, R.id.item_book_last_action)
.check(matches(withText(endsWith(
context.getString(R.string.force_saving_failed, context.getString(R.string.multiple_repos))))));

}

@Test
public void testForceSavingBookWithNoLinkNoRepos() {
testUtils.setupBook("book-one", "First book used for testing\n* Note A");
testUtils.setupBook("book-two", "Second book used for testing\n* Note 1\n* Note 2");
scenario = ActivityScenario.launch(MainActivity.class);

onView(allOf(withText("book-one"), isDisplayed())).perform(longClick());
onView(withId(R.id.books_context_menu_force_save)).perform(click());
onView(withText(R.string.overwrite)).perform(click());
onBook(0, R.id.item_book_last_action)
.check(matches(withText(endsWith(
context.getString(R.string.force_saving_failed, context.getString(R.string.no_repos))))));
}

@Test
public void testForceSavingBookWithNoLinkSingleRepo() {
testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
testUtils.setupBook("book-one", "First book used for testing\n* Note A");
testUtils.setupBook("book-two", "Second book used for testing\n* Note 1\n* Note 2");
scenario = ActivityScenario.launch(MainActivity.class);

onView(allOf(withText("book-one"), isDisplayed())).perform(longClick());
onView(withId(R.id.books_context_menu_force_save)).perform(click());
onView(withText(R.string.overwrite)).perform(click());

onBook(0, R.id.item_book_last_action)
.check(matches(withText(endsWith(
context.getString(R.string.force_saved_to_uri, "mock://repo-a/book-one.org")))));
}

@Test
public void testForceSavingBookWithLink() {
Repo repo = testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
testUtils.setupBook("booky", "First book used for testing\n* Note A", repo);
scenario = ActivityScenario.launch(MainActivity.class);

onView(allOf(withText("booky"), isDisplayed())).perform(longClick());
onView(withId(R.id.books_context_menu_force_save)).perform(click());
onView(withText(R.string.overwrite)).perform(click());

onBook(0, R.id.item_book_last_action)
.check(matches(withText(endsWith(
context.getString(R.string.force_saved_to_uri, "mock://repo-a/booky.org")))));
}

@Test
public void testForceSavingMultipleBooks() {
Repo repo = testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
Expand Down Expand Up @@ -784,21 +640,6 @@ public void testSettingLinkForLoadedOrgTxtBook() {
onBook(0, R.id.item_book_synced_url).check(matches(allOf(withText("mock://repo-a/booky.org.txt"), isDisplayed())));
}

@Test
public void testSpaceSeparatedBookName() {
Repo repo = testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
testUtils.setupRook(repo, "mock://repo-a/Book%20Name.org", "", "1abcdef", 1400067155);

scenario = ActivityScenario.launch(MainActivity.class);

sync();

onBook(0, R.id.item_book_synced_url)
.check(matches(allOf(withText("mock://repo-a/Book%20Name.org"), isDisplayed())));
onBook(0, R.id.item_book_last_action)
.check(matches(allOf(withText(endsWith("Loaded from mock://repo-a/Book%20Name.org")), isDisplayed())));
}

@Test
public void testRenameModifiedBook() {
testUtils.setupRepo(RepoType.MOCK, "mock://repo-a");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public void testLoadRook() throws IOException {
assertEquals("remote-book-1", BookName.fromRook(book.getSyncedTo()).getName());
assertEquals("0abcdef", book.getSyncedTo().getRevision());
assertEquals(1400067156000L, book.getSyncedTo().getMtime());
assertEquals(repo.getUrl(), vrook.getRepoUri().toString());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ public void testListDownloadsDirectory() throws IOException {
assertNotNull(repo.getBooks());
}

// TODO: Do the same for dropbox repo
@Test
public void testRenameBook() {
BookView bookView;
Expand Down
Loading

0 comments on commit d51b62d

Please sign in to comment.