Enable emulator tests (#708)

* github: Enable instrumentation testing in PRs

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* Upgrade to Gradle 6.3

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* Remove outdated and broken tests, redo PasswordEntryTest in Kotlin

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* Remove now unused test assets

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* github: Disable debug APK uploads in PR testing

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* Update .github/workflows/pull_request.yml

* Update .github/workflows/pull_request.yml

Switching to Ubuntu fails due to the SDK 29 missing.

Co-authored-by: Fabian Henneke <FabianHenneke@users.noreply.github.com>
This commit is contained in:
Harsh Shandilya 2020-04-17 00:24:13 +05:30 committed by GitHub
parent 8b4751f825
commit 4ffd7ed9bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 109 additions and 406 deletions

View file

@ -3,7 +3,10 @@ on: pull_request
name: Check pull request
jobs:
test-pr:
runs-on: ubuntu-latest
runs-on: macos-latest
strategy:
matrix:
api-level: [23, 25, 27, 29]
steps:
- uses: actions/checkout@master
@ -16,9 +19,12 @@ jobs:
path: ~/.gradle/caches
key: gradle-${{ runner.os }}-${{ hashFiles('**/build.gradle') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/dependencies.gradle') }}
- run: ./gradlew spotlessCheck assembleDebug testDebug lintDebug -Dpre-dex=false
- name: Run unit tests
run: ./gradlew spotlessCheck testDebug lintDebug -Dpre-dex=false
- uses: actions/upload-artifact@master
- name: Run instrumentation tests
uses: reactivecircus/android-emulator-runner@v2
with:
name: Debug APK
path: app/build/outputs/apk/debug/app-debug.apk
api-level: ${{ matrix.api-level }}
target: default
script: ./gradlew connectedCheck

View file

@ -1,3 +0,0 @@
sub_pass
login: user
sub_extra

View file

@ -1,3 +0,0 @@
password
username: user
extra

View file

@ -1,182 +0,0 @@
/*
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package com.zeapo.pwdstore
import android.annotation.SuppressLint
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.os.SystemClock
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import com.zeapo.pwdstore.crypto.PgpActivity
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
import kotlinx.android.synthetic.main.decrypt_layout.crypto_extra_show
import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_category_decrypt
import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_file
import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_show
import kotlinx.android.synthetic.main.decrypt_layout.crypto_username_show
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.openintents.openpgp.IOpenPgpService2
@RunWith(AndroidJUnit4::class)
@LargeTest
class DecryptTest {
private lateinit var targetContext: Context
private lateinit var testContext: Context
lateinit var activity: PgpActivity
private val name = "sub"
private val parentPath = "/category/"
lateinit var path: String
lateinit var repoPath: String
@Rule @JvmField
var mActivityRule: ActivityTestRule<PgpActivity> = ActivityTestRule<PgpActivity>(PgpActivity::class.java, true, false)
private fun init() {
targetContext = InstrumentationRegistry.getInstrumentation().targetContext
testContext = InstrumentationRegistry.getInstrumentation().context
copyAssets("encrypted-store", File(targetContext.filesDir, "test-store").absolutePath)
repoPath = File(targetContext.filesDir, "test-store").absolutePath
path = "$repoPath/$parentPath/$name.gpg".replace("//", "/")
val intent = Intent(targetContext, PgpActivity::class.java)
intent.putExtra("OPERATION", "DECRYPT")
intent.putExtra("FILE_PATH", path)
intent.putExtra("REPO_PATH", repoPath)
activity = mActivityRule.launchActivity(intent)
}
@Test
fun pathShouldDecompose() {
val pathOne = "/fake/path/cat1/n1.gpg"
val pathTwo = "/fake/path/n2.gpg"
assertEquals("/cat1/n1.gpg", PgpActivity.getRelativePath(pathOne, "/fake/path"))
assertEquals("/cat1/", PgpActivity.getParentPath(pathOne, "/fake/path"))
assertEquals("n1", PgpActivity.getName("$pathOne/fake/path"))
// test that even if we append a `/` it still works
assertEquals("n1", PgpActivity.getName("$pathOne/fake/path/"))
assertEquals("/n2.gpg", PgpActivity.getRelativePath(pathTwo, "/fake/path"))
assertEquals("/", PgpActivity.getParentPath(pathTwo, "/fake/path"))
assertEquals("n2", PgpActivity.getName("$pathTwo/fake/path"))
assertEquals("n2", PgpActivity.getName("$pathTwo/fake/path/"))
}
@Test
fun activityShouldShowName() {
init()
val categoryView = activity.crypto_password_category_decrypt
assertNotNull(categoryView)
assertEquals(parentPath, categoryView.text)
val nameView = activity.crypto_password_file
assertNotNull(nameView)
assertEquals(name, nameView.text)
}
@SuppressLint("ApplySharedPref") // we need the preferences right away
@Test
fun shouldDecrypt() {
init()
val clearPass = IOUtils.toString(testContext.assets.open("clear-store/category/sub"), StandardCharsets.UTF_8)
val passEntry = PasswordEntry(clearPass)
// Setup the timer to 1 second
// first remember the previous timer to set it back later
val showTime = try {
Integer.parseInt(activity.settings.getString("general_show_time", "45") ?: "45")
} catch (e: NumberFormatException) {
45
}
// second set the new timer
activity.settings.edit().putString("general_show_time", "2").commit()
activity.onBound(object : IOpenPgpService2 {
override fun createOutputPipe(p0: Int): ParcelFileDescriptor {
TODO("Not yet implemented")
}
override fun asBinder(): IBinder {
TODO("Not yet implemented")
}
override fun execute(p0: Intent?, p1: ParcelFileDescriptor?, p2: Int): Intent {
TODO("Not yet implemented")
}
})
// have we decrypted things correctly?
assertEquals(passEntry.password, activity.crypto_password_show.text)
assertEquals(passEntry.username, activity.crypto_username_show.text.toString())
assertEquals(passEntry.extraContent, activity.crypto_extra_show.text.toString())
// did we copy the password?
val clipboard: ClipboardManager = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
assertEquals(passEntry.password, clipboard.primaryClip!!.getItemAt(0).text)
// wait until the clipboard is cleared
SystemClock.sleep(4000)
// The clipboard should be cleared!!
for (i in 0..clipboard.primaryClip!!.itemCount) {
assertEquals("", clipboard.primaryClip!!.getItemAt(i).text)
}
// set back the timer
activity.settings.edit().putString("general_show_time", showTime.toString()).commit()
}
companion object {
fun copyAssets(source: String, destination: String) {
FileUtils.forceMkdir(File(destination))
FileUtils.cleanDirectory(File(destination))
val testContext = InstrumentationRegistry.getInstrumentation().context
val assetManager = testContext.assets
val files: Array<String>? = assetManager.list(source)
files?.map { filename ->
val destPath = "$destination/$filename"
val sourcePath = "$source/$filename"
if (assetManager.list(sourcePath)!!.isNotEmpty()) {
FileUtils.forceMkdir(File(destination, filename))
copyAssets("$source/$filename", destPath)
} else {
try {
val input = assetManager.open(sourcePath)
val outFile = File(destination, filename)
val output = FileOutputStream(outFile)
IOUtils.copy(input, output)
input.close()
output.flush()
output.close()
} catch (e: IOException) {
Log.e("tag", "Failed to copy asset file: $filename", e)
}
}
}
}
}
}

View file

@ -1,100 +0,0 @@
/*
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package com.zeapo.pwdstore
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.os.ParcelFileDescriptor
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import com.zeapo.pwdstore.crypto.PgpActivity
import java.io.File
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.openintents.openpgp.IOpenPgpService2
@RunWith(AndroidJUnit4::class)
@LargeTest
class EncryptTest {
private lateinit var targetContext: Context
private lateinit var testContext: Context
private lateinit var activity: PgpActivity
private val name = "sub"
private val parentPath = "/category/"
private lateinit var path: String
private lateinit var repoPath: String
@Rule @JvmField
var mActivityRule: ActivityTestRule<PgpActivity> = ActivityTestRule<PgpActivity>(PgpActivity::class.java, true, false)
private fun init() {
targetContext = InstrumentationRegistry.getInstrumentation().targetContext
testContext = InstrumentationRegistry.getInstrumentation().context
// have an empty store
FileUtils.forceMkdir(File(targetContext.filesDir, "test-store"))
FileUtils.cleanDirectory(File(targetContext.filesDir, "test-store"))
repoPath = File(targetContext.filesDir, "test-store").absolutePath
path = "$repoPath/$parentPath/".replace("//", "/")
val intent = Intent(targetContext, PgpActivity::class.java)
intent.putExtra("OPERATION", "ENCRYPT")
intent.putExtra("FILE_PATH", path)
intent.putExtra("REPO_PATH", repoPath)
activity = mActivityRule.launchActivity(intent)
}
@SuppressLint("ApplySharedPref", "SetTextI18n")
@Test
fun shouldEncrypt() {
init()
onView(withId(R.id.crypto_password_category)).check(ViewAssertions.matches(withText(parentPath)))
activity.onBound(object : IOpenPgpService2 {
override fun createOutputPipe(p0: Int): ParcelFileDescriptor {
TODO("Not yet implemented")
}
override fun asBinder(): IBinder {
TODO("Not yet implemented")
}
override fun execute(p0: Intent?, p1: ParcelFileDescriptor?, p2: Int): Intent {
TODO("Not yet implemented")
}
})
val clearPass = IOUtils.toString(testContext.assets.open("clear-store/category/sub"), Charsets.UTF_8.name())
val passEntry = PasswordEntry(clearPass)
onView(withId(R.id.crypto_password_file_edit)).perform(typeText("sub"))
onView(withId(R.id.crypto_password_edit)).perform(typeText(passEntry.password))
onView(withId(R.id.crypto_extra_edit)).perform(scrollTo(), click())
onView(withId(R.id.crypto_extra_edit)).perform(typeText(passEntry.extraContent))
// we should return to the home screen once we confirm
onView(withId(R.id.crypto_confirm_add)).perform(click())
// The resulting file should exist
assert(File("$path/$name.gpg").exists())
}
}

View file

@ -1,15 +0,0 @@
/*
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package com.zeapo.pwdstore;
import com.zeapo.pwdstore.utils.Otp;
import junit.framework.TestCase;
public class OtpTest extends TestCase {
public void testOtp() {
String code = Otp.calculateCode("JBSWY3DPEHPK3PXP", 0L, "sha1", "s");
assertEquals("282760", code);
}
}

View file

@ -1,97 +0,0 @@
/*
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package com.zeapo.pwdstore;
import junit.framework.TestCase;
public class PasswordEntryTest extends TestCase {
public void testGetPassword() {
assertEquals("fooooo", new PasswordEntry("fooooo\nbla\n").getPassword());
assertEquals("fooooo", new PasswordEntry("fooooo\nbla").getPassword());
assertEquals("fooooo", new PasswordEntry("fooooo\n").getPassword());
assertEquals("fooooo", new PasswordEntry("fooooo").getPassword());
assertEquals("", new PasswordEntry("\nblubb\n").getPassword());
assertEquals("", new PasswordEntry("\nblubb").getPassword());
assertEquals("", new PasswordEntry("\n").getPassword());
assertEquals("", new PasswordEntry("").getPassword());
}
public void testGetExtraContent() {
assertEquals("bla\n", new PasswordEntry("fooooo\nbla\n").getExtraContent());
assertEquals("bla", new PasswordEntry("fooooo\nbla").getExtraContent());
assertEquals("", new PasswordEntry("fooooo\n").getExtraContent());
assertEquals("", new PasswordEntry("fooooo").getExtraContent());
assertEquals("blubb\n", new PasswordEntry("\nblubb\n").getExtraContent());
assertEquals("blubb", new PasswordEntry("\nblubb").getExtraContent());
assertEquals("", new PasswordEntry("\n").getExtraContent());
assertEquals("", new PasswordEntry("").getExtraContent());
}
public void testGetUsername() {
assertEquals(
"username",
new PasswordEntry("secret\nextra\nlogin: username\ncontent\n").getUsername());
assertEquals(
"username",
new PasswordEntry("\nextra\nusername: username\ncontent\n").getUsername());
assertEquals(
"username", new PasswordEntry("\nUSERNaMe: username\ncontent\n").getUsername());
assertEquals("username", new PasswordEntry("\nLOGiN:username").getUsername());
assertNull(new PasswordEntry("secret\nextra\ncontent\n").getUsername());
}
public void testHasUsername() {
assertTrue(new PasswordEntry("secret\nextra\nlogin: username\ncontent\n").hasUsername());
assertFalse(new PasswordEntry("secret\nextra\ncontent\n").hasUsername());
assertFalse(new PasswordEntry("secret\nlogin failed\n").hasUsername());
assertFalse(new PasswordEntry("\n").hasUsername());
assertFalse(new PasswordEntry("").hasUsername());
}
public void testNoTotpUriPresent() {
PasswordEntry entry = new PasswordEntry("secret\nextra\nlogin: username\ncontent");
assertFalse(entry.hasTotp());
assertNull(entry.getTotpSecret());
}
public void testTotpUriInPassword() {
PasswordEntry entry = new PasswordEntry("otpauth://totp/test?secret=JBSWY3DPEHPK3PXP");
assertTrue(entry.hasTotp());
assertEquals("JBSWY3DPEHPK3PXP", entry.getTotpSecret());
}
public void testTotpUriInContent() {
PasswordEntry entry =
new PasswordEntry(
"secret\nusername: test\notpauth://totp/test?secret=JBSWY3DPEHPK3PXP");
assertTrue(entry.hasTotp());
assertEquals("JBSWY3DPEHPK3PXP", entry.getTotpSecret());
}
public void testNoHotpUriPresent() {
PasswordEntry entry = new PasswordEntry("secret\nextra\nlogin: username\ncontent");
assertFalse(entry.hasHotp());
assertNull(entry.getHotpSecret());
assertNull(entry.getHotpCounter());
}
public void testHotpUriInPassword() {
PasswordEntry entry =
new PasswordEntry("otpauth://hotp/test?secret=JBSWY3DPEHPK3PXP&counter=25");
assertTrue(entry.hasHotp());
assertEquals("JBSWY3DPEHPK3PXP", entry.getHotpSecret());
assertEquals(new Long(25), entry.getHotpCounter());
}
public void testHotpUriInContent() {
PasswordEntry entry =
new PasswordEntry(
"secret\nusername: test\notpauth://hotp/test?secret=JBSWY3DPEHPK3PXP&counter=25");
assertTrue(entry.hasHotp());
assertEquals("JBSWY3DPEHPK3PXP", entry.getHotpSecret());
assertEquals(new Long(25), entry.getHotpCounter());
}
}

View file

@ -0,0 +1,97 @@
/*
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package com.zeapo.pwdstore
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
import org.junit.Test
class PasswordEntryTest {
@Test fun testGetPassword() {
assertEquals("fooooo", PasswordEntry("fooooo\nbla\n").password)
assertEquals("fooooo", PasswordEntry("fooooo\nbla").password)
assertEquals("fooooo", PasswordEntry("fooooo\n").password)
assertEquals("fooooo", PasswordEntry("fooooo").password)
assertEquals("", PasswordEntry("\nblubb\n").password)
assertEquals("", PasswordEntry("\nblubb").password)
assertEquals("", PasswordEntry("\n").password)
assertEquals("", PasswordEntry("").password)
}
@Test fun testGetExtraContent() {
assertEquals("bla\n", PasswordEntry("fooooo\nbla\n").extraContent)
assertEquals("bla", PasswordEntry("fooooo\nbla").extraContent)
assertEquals("", PasswordEntry("fooooo\n").extraContent)
assertEquals("", PasswordEntry("fooooo").extraContent)
assertEquals("blubb\n", PasswordEntry("\nblubb\n").extraContent)
assertEquals("blubb", PasswordEntry("\nblubb").extraContent)
assertEquals("", PasswordEntry("\n").extraContent)
assertEquals("", PasswordEntry("").extraContent)
}
@Test fun testGetUsername() {
assertEquals(
"username",
PasswordEntry("secret\nextra\nlogin: username\ncontent\n").username)
assertEquals(
"username",
PasswordEntry("\nextra\nusername: username\ncontent\n").username)
assertEquals(
"username", PasswordEntry("\nUSERNaMe: username\ncontent\n").username)
assertEquals("username", PasswordEntry("\nLOGiN:username").username)
assertNull(PasswordEntry("secret\nextra\ncontent\n").username)
}
@Test fun testHasUsername() {
assertTrue(PasswordEntry("secret\nextra\nlogin: username\ncontent\n").hasUsername())
assertFalse(PasswordEntry("secret\nextra\ncontent\n").hasUsername())
assertFalse(PasswordEntry("secret\nlogin failed\n").hasUsername())
assertFalse(PasswordEntry("\n").hasUsername())
assertFalse(PasswordEntry("").hasUsername())
}
@Test fun testNoTotpUriPresent() {
val entry = PasswordEntry("secret\nextra\nlogin: username\ncontent")
assertFalse(entry.hasTotp())
assertNull(entry.totpSecret)
}
@Test fun testTotpUriInPassword() {
val entry = PasswordEntry("otpauth://totp/test?secret=JBSWY3DPEHPK3PXP")
assertTrue(entry.hasTotp())
assertEquals("JBSWY3DPEHPK3PXP", entry.totpSecret)
}
@Test fun testTotpUriInContent() {
val entry = PasswordEntry(
"secret\nusername: test\notpauth://totp/test?secret=JBSWY3DPEHPK3PXP")
assertTrue(entry.hasTotp())
assertEquals("JBSWY3DPEHPK3PXP", entry.totpSecret)
}
@Test fun testNoHotpUriPresent() {
val entry = PasswordEntry("secret\nextra\nlogin: username\ncontent")
assertFalse(entry.hasHotp())
assertNull(entry.hotpSecret)
assertNull(entry.hotpCounter)
}
@Test fun testHotpUriInPassword() {
val entry = PasswordEntry("otpauth://hotp/test?secret=JBSWY3DPEHPK3PXP&counter=25")
assertTrue(entry.hasHotp())
assertEquals("JBSWY3DPEHPK3PXP", entry.hotpSecret)
assertEquals(25, entry.hotpCounter)
}
@Test fun testHotpUriInContent() {
val entry = PasswordEntry(
"secret\nusername: test\notpauth://hotp/test?secret=JBSWY3DPEHPK3PXP&counter=25")
assertTrue(entry.hasHotp())
assertEquals("JBSWY3DPEHPK3PXP", entry.hotpSecret)
assertEquals(25, entry.hotpCounter)
}
}

Binary file not shown.

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists